Merge branch 'akkoma-fixes-1014-1018' into 'develop'
Status visibility checks for post interactions, stop leaking internal Activity representation (Akkoma PR 1014 and 1018) Closes #3383 See merge request pleroma/pleroma!4400
This commit is contained in:
commit
2f48544937
27 changed files with 1259 additions and 71 deletions
1
changelog.d/ap-c2s-interaction-perms.fix
Normal file
1
changelog.d/ap-c2s-interaction-perms.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
AP C2S: Reject interactions with statuses not visible to Actor
|
||||||
1
changelog.d/mastoapi-interaction-perms.fix
Normal file
1
changelog.d/mastoapi-interaction-perms.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
MastodonAPI: Reject interactions with statuses not visible to user
|
||||||
1
changelog.d/view-internals-leaks.fix
Normal file
1
changelog.d/view-internals-leaks.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ObjectView: Do not leak unsanitized internal representation of non-Create/non-Undo Activities on fetches
|
||||||
|
|
@ -21,7 +21,8 @@ defmodule Pleroma.Constants do
|
||||||
"pleroma_internal",
|
"pleroma_internal",
|
||||||
"generator",
|
"generator",
|
||||||
"rules",
|
"rules",
|
||||||
"language"
|
"language",
|
||||||
|
"voters"
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ defmodule Pleroma.Object do
|
||||||
Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}")
|
Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}")
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalize(_, options \\ [fetch: false, id_only: false])
|
def normalize(_, options \\ [fetch: false])
|
||||||
|
|
||||||
# If we pass an Activity to Object.normalize(), we can try to use the preloaded object.
|
# If we pass an Activity to Object.normalize(), we can try to use the preloaded object.
|
||||||
# Use this whenever possible, especially when walking graphs in an O(N) loop!
|
# Use this whenever possible, especially when walking graphs in an O(N) loop!
|
||||||
|
|
@ -155,9 +155,6 @@ defmodule Pleroma.Object do
|
||||||
|
|
||||||
def normalize(ap_id, options) when is_binary(ap_id) do
|
def normalize(ap_id, options) when is_binary(ap_id) do
|
||||||
cond do
|
cond do
|
||||||
Keyword.get(options, :id_only) ->
|
|
||||||
ap_id
|
|
||||||
|
|
||||||
Keyword.get(options, :fetch) ->
|
Keyword.get(options, :fetch) ->
|
||||||
case Fetcher.fetch_object_from_id(ap_id, options) do
|
case Fetcher.fetch_object_from_id(ap_id, options) do
|
||||||
{:ok, object} -> object
|
{:ok, object} -> object
|
||||||
|
|
|
||||||
|
|
@ -482,6 +482,42 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# We currently lack a Flag ObjectValidator since both CommonAPI and Transmogrifier
|
||||||
|
# both send it straight to ActivityPub.flag and C2S currently has to go through
|
||||||
|
# the normal pipeline which requires an ObjectValidator.
|
||||||
|
# TODO: Add a Flag Activity ObjectValidator
|
||||||
|
defp check_allowed_action(_, %{"type" => "Flag"}) do
|
||||||
|
{:error, "Flag activities aren't currently supported in C2S"}
|
||||||
|
end
|
||||||
|
|
||||||
|
# It would respond with 201 and silently fail with:
|
||||||
|
# Could not decode featured collection at fetch #{user.ap_id} \
|
||||||
|
# {:error, "Trying to fetch local resource"}
|
||||||
|
defp check_allowed_action(%{ap_id: ap_id}, %{"type" => "Update", "object" => %{"id" => ap_id}}),
|
||||||
|
do: {:error, "Updating profile is not currently supported in C2S"}
|
||||||
|
|
||||||
|
defp check_allowed_action(_, activity), do: {:ok, activity}
|
||||||
|
|
||||||
|
defp validate_visibility(%User{} = user, %{"type" => type, "object" => object} = activity) do
|
||||||
|
with {_, %Object{} = normalized_object} <-
|
||||||
|
{:normalize, Object.normalize(object, fetch: false)},
|
||||||
|
{_, true} <- {:visibility, Visibility.visible_for_user?(normalized_object, user)} do
|
||||||
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
{:normalize, _} ->
|
||||||
|
if type in ["Create", "Listen"] do
|
||||||
|
# Creating new object via C2S; user is local and authenticated
|
||||||
|
# via the :authenticate Plug pipeline.
|
||||||
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
{:error, "No such object found"}
|
||||||
|
end
|
||||||
|
|
||||||
|
{:visibility, _} ->
|
||||||
|
{:forbidden, "You can't interact with this object"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def update_outbox(
|
def update_outbox(
|
||||||
%{assigns: %{user: %User{nickname: nickname, ap_id: actor} = user}} = conn,
|
%{assigns: %{user: %User{nickname: nickname, ap_id: actor} = user}} = conn,
|
||||||
%{"nickname" => nickname} = params
|
%{"nickname" => nickname} = params
|
||||||
|
|
@ -493,6 +529,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
|> Map.put("actor", actor)
|
|> Map.put("actor", actor)
|
||||||
|
|
||||||
with {:ok, params} <- fix_user_message(user, params),
|
with {:ok, params} <- fix_user_message(user, params),
|
||||||
|
{:ok, params} <- check_allowed_action(user, params),
|
||||||
|
{:ok, params} <- validate_visibility(user, params),
|
||||||
{:ok, activity, _} <- Pipeline.common_pipeline(params, local: true),
|
{:ok, activity, _} <- Pipeline.common_pipeline(params, local: true),
|
||||||
%Activity{data: activity_data} <- Activity.normalize(activity) do
|
%Activity{data: activity_data} <- Activity.normalize(activity) do
|
||||||
conn
|
conn
|
||||||
|
|
|
||||||
|
|
@ -873,6 +873,27 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
{:ok, data}
|
{:ok, data}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def prepare_outgoing(%{"type" => "Update", "object" => %{"type" => objtype} = object} = data)
|
||||||
|
when objtype in Pleroma.Constants.actor_types() do
|
||||||
|
object =
|
||||||
|
object
|
||||||
|
|> maybe_fix_user_object()
|
||||||
|
|> strip_internal_fields()
|
||||||
|
|
||||||
|
data =
|
||||||
|
data
|
||||||
|
|> Map.put("object", object)
|
||||||
|
|> strip_internal_fields()
|
||||||
|
|> Map.merge(Utils.make_json_ld_header(object))
|
||||||
|
|> Map.delete("bcc")
|
||||||
|
|
||||||
|
{:ok, data}
|
||||||
|
end
|
||||||
|
|
||||||
|
def prepare_outgoing(%{"type" => "Update", "object" => %{}} = data) do
|
||||||
|
raise "Requested to serve an Update for non-updateable object type: #{inspect(data)}"
|
||||||
|
end
|
||||||
|
|
||||||
def prepare_outgoing(%{"type" => "Announce", "actor" => ap_id, "object" => object_id} = data) do
|
def prepare_outgoing(%{"type" => "Announce", "actor" => ap_id, "object" => object_id} = data) do
|
||||||
object =
|
object =
|
||||||
object_id
|
object_id
|
||||||
|
|
|
||||||
|
|
@ -15,26 +15,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
|
||||||
Map.merge(base, additional)
|
Map.merge(base, additional)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("object.json", %{object: %Activity{data: %{"type" => activity_type}} = activity})
|
|
||||||
when activity_type in ["Create", "Listen"] do
|
|
||||||
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header(activity.data)
|
|
||||||
object = Object.normalize(activity, fetch: false)
|
|
||||||
|
|
||||||
additional =
|
|
||||||
Transmogrifier.prepare_object(activity.data)
|
|
||||||
|> Map.put("object", Transmogrifier.prepare_object(object.data))
|
|
||||||
|
|
||||||
Map.merge(base, additional)
|
|
||||||
end
|
|
||||||
|
|
||||||
def render("object.json", %{object: %Activity{} = activity}) do
|
def render("object.json", %{object: %Activity{} = activity}) do
|
||||||
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header(activity.data)
|
{:ok, ap_data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
object_id = Object.normalize(activity, id_only: true)
|
ap_data
|
||||||
|
|
||||||
additional =
|
|
||||||
Transmogrifier.prepare_object(activity.data)
|
|
||||||
|> Map.put("object", object_id)
|
|
||||||
|
|
||||||
Map.merge(base, additional)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do
|
||||||
alias OpenApiSpex.Schema
|
alias OpenApiSpex.Schema
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.Account
|
alias Pleroma.Web.ApiSpec.Schemas.Account
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.ApiNotFoundError
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.Status
|
alias Pleroma.Web.ApiSpec.Schemas.Status
|
||||||
|
|
||||||
|
|
@ -35,7 +36,8 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do
|
||||||
security: [%{"oAuth" => ["read:statuses"]}],
|
security: [%{"oAuth" => ["read:statuses"]}],
|
||||||
operationId: "EmojiReactionController.index",
|
operationId: "EmojiReactionController.index",
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => array_of_reactions_response()
|
200 => array_of_reactions_response(),
|
||||||
|
404 => Operation.response("Access denied", "application/json", ApiNotFoundError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
@ -54,7 +56,8 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do
|
||||||
operationId: "EmojiReactionController.create",
|
operationId: "EmojiReactionController.create",
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => Operation.response("Status", "application/json", Status),
|
200 => Operation.response("Status", "application/json", Status),
|
||||||
400 => Operation.response("Bad Request", "application/json", ApiError)
|
400 => Operation.response("Bad Request", "application/json", ApiError),
|
||||||
|
404 => Operation.response("Not Found", "application/json", ApiNotFoundError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.ApiSpec.ReportOperation do
|
||||||
alias OpenApiSpex.Schema
|
alias OpenApiSpex.Schema
|
||||||
alias Pleroma.Web.ApiSpec.Helpers
|
alias Pleroma.Web.ApiSpec.Helpers
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.ApiNotFoundError
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
|
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
|
||||||
|
|
||||||
def open_api_operation(action) do
|
def open_api_operation(action) do
|
||||||
|
|
@ -24,7 +25,8 @@ defmodule Pleroma.Web.ApiSpec.ReportOperation do
|
||||||
requestBody: Helpers.request_body("Parameters", create_request(), required: true),
|
requestBody: Helpers.request_body("Parameters", create_request(), required: true),
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => Operation.response("Report", "application/json", create_response()),
|
200 => Operation.response("Report", "application/json", create_response()),
|
||||||
400 => Operation.response("Report", "application/json", ApiError)
|
400 => Operation.response("Report", "application/json", ApiError),
|
||||||
|
404 => Operation.response("Report", "application/json", ApiNotFoundError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
||||||
alias Pleroma.Web.ApiSpec.AccountOperation
|
alias Pleroma.Web.ApiSpec.AccountOperation
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.Account
|
alias Pleroma.Web.ApiSpec.Schemas.Account
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.ApiNotFoundError
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.Attachment
|
alias Pleroma.Web.ApiSpec.Schemas.Attachment
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
|
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.Emoji
|
alias Pleroma.Web.ApiSpec.Schemas.Emoji
|
||||||
|
|
@ -177,6 +178,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
||||||
parameters: [id_param()],
|
parameters: [id_param()],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => status_response(),
|
200 => status_response(),
|
||||||
|
400 => Operation.response("Error", "application/json", ApiError),
|
||||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -242,14 +244,19 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
||||||
"error" => "You have already pinned the maximum number of statuses"
|
"error" => "You have already pinned the maximum number of statuses"
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
404 =>
|
404 => Operation.response("Not found", "application/json", ApiNotFoundError),
|
||||||
Operation.response("Not found", "application/json", %Schema{
|
422 =>
|
||||||
allOf: [ApiError],
|
Operation.response(
|
||||||
title: "Unprocessable Entity",
|
"Unprocessable Entity",
|
||||||
example: %{
|
"application/json",
|
||||||
"error" => "Record not found"
|
%Schema{
|
||||||
|
allOf: [ApiError],
|
||||||
|
title: "Unprocessable Entity",
|
||||||
|
example: %{
|
||||||
|
"error" => "Someone else's status cannot be unpinned"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
@ -275,7 +282,8 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => status_response()
|
200 => status_response(),
|
||||||
|
404 => Operation.response("Not found", "application/json", ApiNotFoundError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
@ -289,7 +297,8 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
||||||
operationId: "StatusController.unbookmark",
|
operationId: "StatusController.unbookmark",
|
||||||
parameters: [id_param()],
|
parameters: [id_param()],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => status_response()
|
200 => status_response(),
|
||||||
|
404 => Operation.response("Not found", "application/json", ApiNotFoundError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
@ -324,7 +333,8 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
||||||
],
|
],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => status_response(),
|
200 => status_response(),
|
||||||
400 => Operation.response("Error", "application/json", ApiError)
|
400 => Operation.response("Error", "application/json", ApiError),
|
||||||
|
404 => Operation.response("Not found", "application/json", ApiNotFoundError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
@ -340,7 +350,8 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
||||||
parameters: [id_param()],
|
parameters: [id_param()],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => status_response(),
|
200 => status_response(),
|
||||||
400 => Operation.response("Error", "application/json", ApiError)
|
400 => Operation.response("Error", "application/json", ApiError),
|
||||||
|
404 => Operation.response("Not Found", "application/json", ApiNotFoundError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
19
lib/pleroma/web/api_spec/schemas/api_not_found_error.ex
Normal file
19
lib/pleroma/web/api_spec/schemas/api_not_found_error.ex
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ApiSpec.Schemas.ApiNotFoundError do
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "Not Found",
|
||||||
|
description: "Response schema for 404 API errors",
|
||||||
|
type: :object,
|
||||||
|
properties: %{error: %Schema{type: :string}},
|
||||||
|
example: %{
|
||||||
|
"error" => "Record not found"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
@ -269,6 +269,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
|
|
||||||
defp favorite_helper(user, id) do
|
defp favorite_helper(user, id) do
|
||||||
with {_, %Activity{object: object}} <- {:find_object, Activity.get_by_id_with_object(id)},
|
with {_, %Activity{object: object}} <- {:find_object, Activity.get_by_id_with_object(id)},
|
||||||
|
{_, true} <- {:visibility_error, activity_visible_to_actor(object, user)},
|
||||||
{_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)},
|
{_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)},
|
||||||
{_, {:ok, %Activity{} = activity, _meta}} <-
|
{_, {:ok, %Activity{} = activity, _meta}} <-
|
||||||
{:common_pipeline,
|
{:common_pipeline,
|
||||||
|
|
@ -278,6 +279,9 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
{:find_object, _} ->
|
{:find_object, _} ->
|
||||||
{:error, :not_found}
|
{:error, :not_found}
|
||||||
|
|
||||||
|
{:visibility_error, _} ->
|
||||||
|
{:error, :not_found}
|
||||||
|
|
||||||
{:common_pipeline, {:error, {:validate, {:error, changeset}}}} = e ->
|
{:common_pipeline, {:error, {:validate, {:error, changeset}}}} = e ->
|
||||||
if {:object, {"already liked by this actor", []}} in changeset.errors do
|
if {:object, {"already liked by this actor", []}} in changeset.errors do
|
||||||
{:ok, :already_liked}
|
{:ok, :already_liked}
|
||||||
|
|
@ -296,6 +300,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
|
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
|
||||||
{:find_activity, Activity.get_by_id(id)},
|
{:find_activity, Activity.get_by_id(id)},
|
||||||
%Object{} = note <- Object.normalize(activity, fetch: false),
|
%Object{} = note <- Object.normalize(activity, fetch: false),
|
||||||
|
{_, true} <- {:visibility_error, activity_visible_to_actor(note, user)},
|
||||||
%Activity{} = like <- Utils.get_existing_like(user.ap_id, note),
|
%Activity{} = like <- Utils.get_existing_like(user.ap_id, note),
|
||||||
{_, {:ok, _}} <- {:cancel_jobs, maybe_cancel_jobs(like)},
|
{_, {:ok, _}} <- {:cancel_jobs, maybe_cancel_jobs(like)},
|
||||||
{:ok, undo, _} <- Builder.undo(user, like),
|
{:ok, undo, _} <- Builder.undo(user, like),
|
||||||
|
|
@ -303,6 +308,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
{:find_activity, _} -> {:error, :not_found}
|
{:find_activity, _} -> {:error, :not_found}
|
||||||
|
{:visibility_error, _} -> {:error, :not_found}
|
||||||
_ -> {:error, dgettext("errors", "Could not unfavorite")}
|
_ -> {:error, dgettext("errors", "Could not unfavorite")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -311,11 +317,15 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
{:ok, Activity.t()} | {:error, String.t()}
|
{:ok, Activity.t()} | {:error, String.t()}
|
||||||
def react_with_emoji(id, user, emoji) do
|
def react_with_emoji(id, user, emoji) do
|
||||||
with %Activity{} = activity <- Activity.get_by_id(id),
|
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||||
|
{_, true} <- {:visibility_error, activity_visible_to_actor(activity, user)},
|
||||||
object <- Object.normalize(activity, fetch: false),
|
object <- Object.normalize(activity, fetch: false),
|
||||||
{:ok, emoji_react, _} <- Builder.emoji_react(user, object, emoji),
|
{:ok, emoji_react, _} <- Builder.emoji_react(user, object, emoji),
|
||||||
{:ok, activity, _} <- Pipeline.common_pipeline(emoji_react, local: true) do
|
{:ok, activity, _} <- Pipeline.common_pipeline(emoji_react, local: true) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
|
{:visibility_error, _} ->
|
||||||
|
{:error, :not_found}
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
{:error, dgettext("errors", "Could not add reaction emoji")}
|
{:error, dgettext("errors", "Could not add reaction emoji")}
|
||||||
end
|
end
|
||||||
|
|
@ -506,6 +516,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
@spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
|
@spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
|
||||||
def pin(id, %User{} = user) do
|
def pin(id, %User{} = user) do
|
||||||
with %Activity{} = activity <- create_activity_by_id(id),
|
with %Activity{} = activity <- create_activity_by_id(id),
|
||||||
|
true <- activity_visible_to_actor(activity, user),
|
||||||
true <- activity_belongs_to_actor(activity, user.ap_id),
|
true <- activity_belongs_to_actor(activity, user.ap_id),
|
||||||
true <- object_type_is_allowed_for_pin(activity.object),
|
true <- object_type_is_allowed_for_pin(activity.object),
|
||||||
true <- activity_is_public(activity),
|
true <- activity_is_public(activity),
|
||||||
|
|
@ -531,6 +542,14 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
defp activity_belongs_to_actor(%{actor: actor}, actor), do: true
|
defp activity_belongs_to_actor(%{actor: actor}, actor), do: true
|
||||||
defp activity_belongs_to_actor(_, _), do: {:error, :ownership_error}
|
defp activity_belongs_to_actor(_, _), do: {:error, :ownership_error}
|
||||||
|
|
||||||
|
defp activity_visible_to_actor(activity, %User{} = user) do
|
||||||
|
if Visibility.visible_for_user?(activity, user) do
|
||||||
|
true
|
||||||
|
else
|
||||||
|
{:error, :visibility_error}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp object_type_is_allowed_for_pin(%{data: %{"type" => type}}) do
|
defp object_type_is_allowed_for_pin(%{data: %{"type" => type}}) do
|
||||||
with false <- type in ["Note", "Article", "Question"] do
|
with false <- type in ["Note", "Article", "Question"] do
|
||||||
{:error, :not_allowed}
|
{:error, :not_allowed}
|
||||||
|
|
@ -539,13 +558,18 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
|
|
||||||
defp activity_is_public(activity) do
|
defp activity_is_public(activity) do
|
||||||
with false <- Visibility.public?(activity) do
|
with false <- Visibility.public?(activity) do
|
||||||
{:error, :visibility_error}
|
{:error, :non_public_error}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec unpin(String.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
|
@spec unpin(String.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
|
||||||
def unpin(id, user) do
|
def unpin(id, user) do
|
||||||
|
# Order of visibility/belonging matters for MastoAPI responses.
|
||||||
|
# post not visible -> 404
|
||||||
|
# post visible, not owned -> 422
|
||||||
with %Activity{} = activity <- create_activity_by_id(id),
|
with %Activity{} = activity <- create_activity_by_id(id),
|
||||||
|
true <- activity_visible_to_actor(activity, user),
|
||||||
|
true <- activity_belongs_to_actor(activity, user.ap_id),
|
||||||
{:ok, unpin_data, _} <- Builder.unpin(user, activity.object),
|
{:ok, unpin_data, _} <- Builder.unpin(user, activity.object),
|
||||||
{:ok, _unpin, _} <-
|
{:ok, _unpin, _} <-
|
||||||
Pipeline.common_pipeline(unpin_data,
|
Pipeline.common_pipeline(unpin_data,
|
||||||
|
|
@ -562,7 +586,8 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
def add_mute(activity, user, params \\ %{}) do
|
def add_mute(activity, user, params \\ %{}) do
|
||||||
expires_in = Map.get(params, :expires_in, 0)
|
expires_in = Map.get(params, :expires_in, 0)
|
||||||
|
|
||||||
with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]),
|
with true <- activity_visible_to_actor(activity, user),
|
||||||
|
{:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]),
|
||||||
_ <- Pleroma.Notification.mark_context_as_read(user, activity.data["context"]) do
|
_ <- Pleroma.Notification.mark_context_as_read(user, activity.data["context"]) do
|
||||||
if expires_in > 0 do
|
if expires_in > 0 do
|
||||||
Pleroma.Workers.MuteExpireWorker.new(
|
Pleroma.Workers.MuteExpireWorker.new(
|
||||||
|
|
@ -574,14 +599,21 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
|
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
|
{:error, :visibility_error} -> {:error, :visibility_error}
|
||||||
{:error, _} -> {:error, dgettext("errors", "conversation is already muted")}
|
{:error, _} -> {:error, dgettext("errors", "conversation is already muted")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec remove_mute(Activity.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
@spec remove_mute(Activity.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||||
def remove_mute(%Activity{} = activity, %User{} = user) do
|
def remove_mute(%Activity{} = activity, %User{} = user) do
|
||||||
ThreadMute.remove_mute(user.id, activity.data["context"])
|
case activity_visible_to_actor(activity, user) do
|
||||||
{:ok, activity}
|
true ->
|
||||||
|
ThreadMute.remove_mute(user.id, activity.data["context"])
|
||||||
|
{:ok, activity}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec remove_mute(String.t(), String.t()) :: {:ok, Activity.t()} | {:error, any()}
|
@spec remove_mute(String.t(), String.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||||
|
|
@ -612,6 +644,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
with {:ok, account} <- get_reported_account(data.account_id),
|
with {:ok, account} <- get_reported_account(data.account_id),
|
||||||
{:ok, {content_html, _, _}} <- make_report_content_html(data[:comment]),
|
{:ok, {content_html, _, _}} <- make_report_content_html(data[:comment]),
|
||||||
{:ok, statuses} <- get_report_statuses(account, data),
|
{:ok, statuses} <- get_report_statuses(account, data),
|
||||||
|
true <- check_statuses_visibility(user, statuses),
|
||||||
rules <- get_report_rules(Map.get(data, :rule_ids, nil)) do
|
rules <- get_report_rules(Map.get(data, :rule_ids, nil)) do
|
||||||
ActivityPub.flag(%{
|
ActivityPub.flag(%{
|
||||||
context: Utils.generate_context_id(),
|
context: Utils.generate_context_id(),
|
||||||
|
|
@ -622,9 +655,27 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
forward: Map.get(data, :forward, false),
|
forward: Map.get(data, :forward, false),
|
||||||
rules: rules
|
rules: rules
|
||||||
})
|
})
|
||||||
|
else
|
||||||
|
false ->
|
||||||
|
{:error, :visibility_error}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp check_statuses_visibility(user, statuses) when is_list(statuses) do
|
||||||
|
visibility = for status <- statuses, do: Visibility.visible_for_user?(status, user)
|
||||||
|
|
||||||
|
case Enum.all?(visibility) do
|
||||||
|
true -> true
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# There are no statuses associated with the report, pass!
|
||||||
|
defp check_statuses_visibility(_, status) when status == nil, do: true
|
||||||
|
|
||||||
defp get_reported_account(account_id) do
|
defp get_reported_account(account_id) do
|
||||||
case User.get_cached_by_id(account_id) do
|
case User.get_cached_by_id(account_id) do
|
||||||
%User{} = account -> {:ok, account}
|
%User{} = account -> {:ok, account}
|
||||||
|
|
|
||||||
|
|
@ -136,22 +136,34 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
|
|
||||||
defp in_reply_to(%{params: %{in_reply_to_status_id: ""}} = draft), do: draft
|
defp in_reply_to(%{params: %{in_reply_to_status_id: ""}} = draft), do: draft
|
||||||
|
|
||||||
defp in_reply_to(%{params: %{in_reply_to_status_id: :deleted}} = draft) do
|
defp in_reply_to(%{params: %{in_reply_to_status_id: id}} = draft) when is_binary(id) do
|
||||||
add_error(draft, dgettext("errors", "Cannot reply to a deleted status"))
|
# If a post was deleted all its activities (except the newly added Delete) are purged too,
|
||||||
end
|
# thus lookup by Create db ID will yield nil just as if it never existed in the first place.
|
||||||
|
#
|
||||||
|
# We allow replying to Announce here, due to a Pleroma-FE quirk where if presented with
|
||||||
|
# an Announce id it will render it as if it was just the normal referenced post, but
|
||||||
|
# use the Announce id for replies in the in_reply_to_id key of a POST request to
|
||||||
|
# /api/v1/statuses, or as an :id in /api/v1/statuses/:id/*.
|
||||||
|
# TODO: Fix this quirk in FE and remove here and other affected places
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||||
|
true <- Visibility.visible_for_user?(activity, draft.user),
|
||||||
|
{_, type} when type in ["Create", "Announce"] <- {:type, activity.data["type"]} do
|
||||||
|
%__MODULE__{draft | in_reply_to: activity}
|
||||||
|
else
|
||||||
|
nil ->
|
||||||
|
add_error(draft, dgettext("errors", "Cannot reply to a deleted status"))
|
||||||
|
|
||||||
defp in_reply_to(%{params: %{in_reply_to_status_id: id} = params} = draft) when is_binary(id) do
|
false ->
|
||||||
activity = Activity.get_by_id(id)
|
add_error(draft, dgettext("errors", "Record not found"))
|
||||||
|
|
||||||
params =
|
{:type, type} ->
|
||||||
if is_nil(activity) do
|
add_error(
|
||||||
# Deleted activities are returned as nil
|
draft,
|
||||||
Map.put(params, :in_reply_to_status_id, :deleted)
|
dgettext("errors", "Can only reply to posts, not %{type} activities",
|
||||||
else
|
type: inspect(type)
|
||||||
Map.put(params, :in_reply_to_status_id, activity)
|
)
|
||||||
end
|
)
|
||||||
|
end
|
||||||
in_reply_to(%{draft | params: params})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp in_reply_to(%{params: %{in_reply_to_status_id: %Activity{} = in_reply_to}} = draft) do
|
defp in_reply_to(%{params: %{in_reply_to_status_id: %Activity{} = in_reply_to}} = draft) do
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,12 @@ defmodule Pleroma.Web.MastodonAPI.ReportController do
|
||||||
def create(%{assigns: %{user: user}, body_params: params} = conn, _) do
|
def create(%{assigns: %{user: user}, body_params: params} = conn, _) do
|
||||||
with {:ok, activity} <- Pleroma.Web.CommonAPI.report(user, params) do
|
with {:ok, activity} <- Pleroma.Web.CommonAPI.report(user, params) do
|
||||||
render(conn, "show.json", activity: activity)
|
render(conn, "show.json", activity: activity)
|
||||||
|
else
|
||||||
|
{:error, :visibility_error} ->
|
||||||
|
{:error, :not_found, "Record not found"}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -319,6 +319,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
||||||
@doc "DELETE /api/v1/statuses/:id"
|
@doc "DELETE /api/v1/statuses/:id"
|
||||||
def delete(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do
|
def delete(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do
|
||||||
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
|
# CommonAPI already checks whether user is allowed to delete
|
||||||
{:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
{:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
||||||
try_render(conn, "show.json",
|
try_render(conn, "show.json",
|
||||||
activity: activity,
|
activity: activity,
|
||||||
|
|
@ -340,6 +341,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
||||||
_
|
_
|
||||||
) do
|
) do
|
||||||
with {:ok, announce} <- CommonAPI.repeat(ap_id_or_id, user, params),
|
with {:ok, announce} <- CommonAPI.repeat(ap_id_or_id, user, params),
|
||||||
|
# CommonAPI already checks whether user is allowed to reblog
|
||||||
%Activity{} = announce <- Activity.normalize(announce.data) do
|
%Activity{} = announce <- Activity.normalize(announce.data) do
|
||||||
try_render(conn, "show.json", %{activity: announce, for: user, as: :activity})
|
try_render(conn, "show.json", %{activity: announce, for: user, as: :activity})
|
||||||
end
|
end
|
||||||
|
|
@ -364,6 +366,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
||||||
_
|
_
|
||||||
) do
|
) do
|
||||||
with {:ok, _fav} <- CommonAPI.favorite(activity_id, user),
|
with {:ok, _fav} <- CommonAPI.favorite(activity_id, user),
|
||||||
|
# CommonAPI already checks whether user is allowed to reblog
|
||||||
%Activity{} = activity <- Activity.get_by_id(activity_id) do
|
%Activity{} = activity <- Activity.get_by_id(activity_id) do
|
||||||
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
end
|
end
|
||||||
|
|
@ -390,6 +393,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
||||||
with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do
|
with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do
|
||||||
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
else
|
else
|
||||||
|
# Order matters, if status is not owned by user and is not visible to user
|
||||||
|
# return 404 just like other endpoints
|
||||||
{:error, :pinned_statuses_limit_reached} ->
|
{:error, :pinned_statuses_limit_reached} ->
|
||||||
{:error, "You have already pinned the maximum number of statuses"}
|
{:error, "You have already pinned the maximum number of statuses"}
|
||||||
|
|
||||||
|
|
@ -397,6 +402,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
||||||
{:error, :unprocessable_entity, "Someone else's status cannot be pinned"}
|
{:error, :unprocessable_entity, "Someone else's status cannot be pinned"}
|
||||||
|
|
||||||
{:error, :visibility_error} ->
|
{:error, :visibility_error} ->
|
||||||
|
{:error, :not_found, "Record not found"}
|
||||||
|
|
||||||
|
{:error, :non_public_error} ->
|
||||||
{:error, :unprocessable_entity, "Non-public status cannot be pinned"}
|
{:error, :unprocessable_entity, "Non-public status cannot be pinned"}
|
||||||
|
|
||||||
error ->
|
error ->
|
||||||
|
|
@ -410,8 +418,20 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
||||||
conn,
|
conn,
|
||||||
_
|
_
|
||||||
) do
|
) do
|
||||||
|
# CommonAPI already checks whether user can unpin
|
||||||
with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do
|
with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do
|
||||||
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
else
|
||||||
|
# Order matters, if status is not owned by user and is not visible to user
|
||||||
|
# return 404 just like other endpoints
|
||||||
|
{:error, :visibility_error} ->
|
||||||
|
{:error, :not_found, "Record not found"}
|
||||||
|
|
||||||
|
{:error, :ownership_error} ->
|
||||||
|
{:error, :unprocessable_entity, "Someone else's status cannot be unpinned"}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -434,6 +454,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
||||||
),
|
),
|
||||||
{:ok, _bookmark} <- Bookmark.create(user.id, activity.id, folder_id) do
|
{:ok, _bookmark} <- Bookmark.create(user.id, activity.id, folder_id) do
|
||||||
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
else
|
||||||
|
false ->
|
||||||
|
{:error, :not_found, "Record not found"}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -447,6 +473,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
||||||
true <- Visibility.visible_for_user?(activity, user),
|
true <- Visibility.visible_for_user?(activity, user),
|
||||||
{:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do
|
{:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do
|
||||||
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
else
|
||||||
|
false ->
|
||||||
|
{:error, :not_found, "Record not found"}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -459,8 +491,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
||||||
_
|
_
|
||||||
) do
|
) do
|
||||||
with %Activity{} = activity <- Activity.get_by_id(id),
|
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||||
|
# CommonAPI already checks whether user is allowed to mute
|
||||||
{:ok, activity} <- CommonAPI.add_mute(activity, user, params) do
|
{:ok, activity} <- CommonAPI.add_mute(activity, user, params) do
|
||||||
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
else
|
||||||
|
{:error, :visibility_error} ->
|
||||||
|
{:error, :not_found, "Record not found"}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -473,8 +512,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
||||||
_
|
_
|
||||||
) do
|
) do
|
||||||
with %Activity{} = activity <- Activity.get_by_id(id),
|
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||||
|
# CommonAPI already checks whether user is allowed to unmute
|
||||||
{:ok, activity} <- CommonAPI.remove_mute(activity, user) do
|
{:ok, activity} <- CommonAPI.remove_mute(activity, user) do
|
||||||
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
else
|
||||||
|
{:error, :visibility_error} ->
|
||||||
|
{:error, :not_found, "Record not found"}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||||
|
|
@ -28,6 +29,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
|
||||||
def index(%{assigns: %{user: user}} = conn, %{id: activity_id} = params) do
|
def index(%{assigns: %{user: user}} = conn, %{id: activity_id} = params) do
|
||||||
with true <- Pleroma.Config.get([:instance, :show_reactions]),
|
with true <- Pleroma.Config.get([:instance, :show_reactions]),
|
||||||
%Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
|
%Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
|
||||||
|
{_, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
|
||||||
%Object{} = object <- Object.normalize(activity, fetch: false),
|
%Object{} = object <- Object.normalize(activity, fetch: false),
|
||||||
reactions <- Object.get_emoji_reactions(object) do
|
reactions <- Object.get_emoji_reactions(object) do
|
||||||
reactions =
|
reactions =
|
||||||
|
|
@ -37,6 +39,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
|
||||||
|
|
||||||
render(conn, "index.json", emoji_reactions: reactions, user: user)
|
render(conn, "index.json", emoji_reactions: reactions, user: user)
|
||||||
else
|
else
|
||||||
|
{:visible, _} -> {:error, :not_found}
|
||||||
_e -> json(conn, [])
|
_e -> json(conn, [])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -76,6 +79,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
|
||||||
|> Pleroma.Emoji.fully_qualify_emoji()
|
|> Pleroma.Emoji.fully_qualify_emoji()
|
||||||
|> Pleroma.Emoji.maybe_quote()
|
|> Pleroma.Emoji.maybe_quote()
|
||||||
|
|
||||||
|
# CommonAPI checks if allowed to react
|
||||||
with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji) do
|
with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji) do
|
||||||
activity = Activity.get_by_id(activity_id)
|
activity = Activity.get_by_id(activity_id)
|
||||||
|
|
||||||
|
|
@ -91,6 +95,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
|
||||||
|> Pleroma.Emoji.fully_qualify_emoji()
|
|> Pleroma.Emoji.fully_qualify_emoji()
|
||||||
|> Pleroma.Emoji.maybe_quote()
|
|> Pleroma.Emoji.maybe_quote()
|
||||||
|
|
||||||
|
# CommonAPI checks only author can revoke reactions
|
||||||
with {:ok, _activity} <- CommonAPI.unreact_with_emoji(activity_id, user, emoji) do
|
with {:ok, _activity} <- CommonAPI.unreact_with_emoji(activity_id, user, emoji) do
|
||||||
activity = Activity.get_by_id(activity_id)
|
activity = Activity.get_by_id(activity_id)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -332,7 +332,7 @@ defmodule Pleroma.Conversation.ParticipationTest do
|
||||||
# When it's a reply from the blocked user
|
# When it's a reply from the blocked user
|
||||||
{:ok, _direct2} =
|
{:ok, _direct2} =
|
||||||
CommonAPI.post(blocked, %{
|
CommonAPI.post(blocked, %{
|
||||||
status: "reply",
|
status: "@#{third_user.nickname}, #{blocker.nickname} reply",
|
||||||
visibility: "direct",
|
visibility: "direct",
|
||||||
in_reply_to_conversation_id: blocked_participation.id
|
in_reply_to_conversation_id: blocked_participation.id
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,10 @@ defmodule Pleroma.ConversationTest do
|
||||||
jafnhar = insert(:user, local: false)
|
jafnhar = insert(:user, local: false)
|
||||||
tridi = insert(:user)
|
tridi = insert(:user)
|
||||||
|
|
||||||
|
to = [har.nickname, jafnhar.nickname, tridi.nickname]
|
||||||
|
|
||||||
{:ok, activity} =
|
{:ok, activity} =
|
||||||
CommonAPI.post(har, %{status: "Hey @#{jafnhar.nickname}", visibility: "direct"})
|
CommonAPI.post(har, %{status: "Hey @#{jafnhar.nickname}", visibility: "direct", to: to})
|
||||||
|
|
||||||
object = Pleroma.Object.normalize(activity, fetch: false)
|
object = Pleroma.Object.normalize(activity, fetch: false)
|
||||||
context = object.data["context"]
|
context = object.data["context"]
|
||||||
|
|
@ -88,7 +90,8 @@ defmodule Pleroma.ConversationTest do
|
||||||
CommonAPI.post(jafnhar, %{
|
CommonAPI.post(jafnhar, %{
|
||||||
status: "Hey @#{har.nickname}",
|
status: "Hey @#{har.nickname}",
|
||||||
visibility: "direct",
|
visibility: "direct",
|
||||||
in_reply_to_status_id: activity.id
|
in_reply_to_status_id: activity.id,
|
||||||
|
to: to
|
||||||
})
|
})
|
||||||
|
|
||||||
object = Pleroma.Object.normalize(activity, fetch: false)
|
object = Pleroma.Object.normalize(activity, fetch: false)
|
||||||
|
|
@ -112,7 +115,8 @@ defmodule Pleroma.ConversationTest do
|
||||||
CommonAPI.post(tridi, %{
|
CommonAPI.post(tridi, %{
|
||||||
status: "Hey @#{har.nickname}",
|
status: "Hey @#{har.nickname}",
|
||||||
visibility: "direct",
|
visibility: "direct",
|
||||||
in_reply_to_status_id: activity.id
|
in_reply_to_status_id: activity.id,
|
||||||
|
to: to
|
||||||
})
|
})
|
||||||
|
|
||||||
object = Pleroma.Object.normalize(activity, fetch: false)
|
object = Pleroma.Object.normalize(activity, fetch: false)
|
||||||
|
|
|
||||||
|
|
@ -1580,6 +1580,41 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
assert object["content"] == activity["object"]["content"]
|
assert object["content"] == activity["object"]["content"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it inserts an incoming reply create activity into the database", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
replying_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "cofe"})
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
type: "Create",
|
||||||
|
object: %{
|
||||||
|
to: [Pleroma.Constants.as_public(), user.ap_id],
|
||||||
|
cc: [replying_user.follower_address],
|
||||||
|
inReplyTo: activity.object.data["id"],
|
||||||
|
content: "green tea",
|
||||||
|
type: "Note"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> assign(:user, replying_user)
|
||||||
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|
|> post("/users/#{replying_user.nickname}/outbox", data)
|
||||||
|
|> json_response(201)
|
||||||
|
|
||||||
|
updated_object = Object.normalize(activity.object.data["id"], fetch: false)
|
||||||
|
|
||||||
|
assert Activity.get_by_ap_id(result["id"])
|
||||||
|
assert result["object"]
|
||||||
|
assert %Object{data: object} = Object.normalize(result["object"], fetch: false)
|
||||||
|
assert object["content"] == data.object.content
|
||||||
|
assert Pleroma.Web.ActivityPub.Visibility.public?(object)
|
||||||
|
assert object["inReplyTo"] == activity.object.data["id"]
|
||||||
|
assert updated_object.data["repliesCount"] == 1
|
||||||
|
end
|
||||||
|
|
||||||
test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
|
test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
|
|
@ -1706,6 +1741,289 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
assert note_object == Object.normalize(note_activity, fetch: false)
|
assert note_object == Object.normalize(note_activity, fetch: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it rejects Add to other user's collection", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
target_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "Post"})
|
||||||
|
object = Object.normalize(activity, fetch: false)
|
||||||
|
object_id = object.data["id"]
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
type: "Add",
|
||||||
|
target:
|
||||||
|
"#{Pleroma.Web.Endpoint.url()}/users/#{target_user.nickname}/collections/featured",
|
||||||
|
object: object_id
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|
|> post("/users/#{user.nickname}/outbox", data)
|
||||||
|
|
||||||
|
assert json_response(conn, 400)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it rejects Remove to other user's collection", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
target_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "Post"})
|
||||||
|
object = Object.normalize(activity, fetch: false)
|
||||||
|
object_id = object.data["id"]
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
type: "Remove",
|
||||||
|
target:
|
||||||
|
"#{Pleroma.Web.Endpoint.url()}/users/#{target_user.nickname}/collections/featured",
|
||||||
|
object: object_id
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|
|> post("/users/#{user.nickname}/outbox", data)
|
||||||
|
|
||||||
|
assert json_response(conn, 400)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it rejects updating Actor's profile", %{conn: conn} do
|
||||||
|
user = insert(:user, local: true)
|
||||||
|
|
||||||
|
user_object = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
|
||||||
|
user_object_new = Map.put(user_object, "name", "lain")
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
type: "Update",
|
||||||
|
object: user_object_new
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/users/#{user.nickname}/outbox", data)
|
||||||
|
|
||||||
|
updated_user_object = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
|
||||||
|
|
||||||
|
assert updated_user_object == user_object
|
||||||
|
assert json_response(conn, 400)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Actor publicKey tests are redundant with above test,
|
||||||
|
# left here for the case that Updating Actors is ever supported
|
||||||
|
test "it rejects updating Actor's publicKey", %{conn: conn} do
|
||||||
|
user = insert(:user, local: true)
|
||||||
|
|
||||||
|
{:ok, pem} = Pleroma.Keys.generate_rsa_pem()
|
||||||
|
{:ok, _, public_key} = Pleroma.Keys.keys_from_pem(pem)
|
||||||
|
# Taken from UserView
|
||||||
|
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
|
||||||
|
public_key = :public_key.pem_encode([public_key])
|
||||||
|
|
||||||
|
user_object = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
|
||||||
|
user_object_public_key = Map.fetch!(user_object, "publicKey")
|
||||||
|
user_object_public_key = Map.put(user_object_public_key, "publicKeyPem", public_key)
|
||||||
|
user_object_new = Map.put(user_object, "publicKey", user_object_public_key)
|
||||||
|
|
||||||
|
refute user_object == user_object_new
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
type: "Update",
|
||||||
|
object: user_object_new
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/users/#{user.nickname}/outbox", data)
|
||||||
|
|
||||||
|
new_user_object = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
|
||||||
|
|
||||||
|
assert user_object == new_user_object
|
||||||
|
assert json_response(conn, 400)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it rejects updating Actor's publicKey of another user", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
target_user = insert(:user, local: true)
|
||||||
|
|
||||||
|
{:ok, pem} = Pleroma.Keys.generate_rsa_pem()
|
||||||
|
{:ok, _, public_key} = Pleroma.Keys.keys_from_pem(pem)
|
||||||
|
# Taken from UserView
|
||||||
|
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
|
||||||
|
public_key = :public_key.pem_encode([public_key])
|
||||||
|
|
||||||
|
target_user_object =
|
||||||
|
Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: target_user})
|
||||||
|
|
||||||
|
target_user_object_public_key = Map.fetch!(target_user_object, "publicKey")
|
||||||
|
|
||||||
|
target_user_object_public_key =
|
||||||
|
Map.put(target_user_object_public_key, "publicKeyPem", public_key)
|
||||||
|
|
||||||
|
target_user_object_new =
|
||||||
|
Map.put(target_user_object, "publicKey", target_user_object_public_key)
|
||||||
|
|
||||||
|
refute target_user_object == target_user_object_new
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
type: "Update",
|
||||||
|
object: target_user_object_new
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/users/#{target_user.nickname}/outbox", data)
|
||||||
|
|
||||||
|
new_target_user_object =
|
||||||
|
Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: target_user})
|
||||||
|
|
||||||
|
assert target_user_object == new_target_user_object
|
||||||
|
assert json_response(conn, 403)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it rejects creating Actors of type Application", %{conn: conn} do
|
||||||
|
user = insert(:user, local: true)
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
type: "Create",
|
||||||
|
object: %{
|
||||||
|
type: "Application"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/users/#{user.nickname}/outbox", data)
|
||||||
|
|
||||||
|
assert json_response(conn, 400)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it rejects creating Actors of type Person", %{conn: conn} do
|
||||||
|
user = insert(:user, local: true)
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
type: "Create",
|
||||||
|
object: %{
|
||||||
|
type: "Person"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/users/#{user.nickname}/outbox", data)
|
||||||
|
|
||||||
|
assert json_response(conn, 400)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it rejects creating Actors of type Service", %{conn: conn} do
|
||||||
|
user = insert(:user, local: true)
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
type: "Create",
|
||||||
|
object: %{
|
||||||
|
type: "Service"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/users/#{user.nickname}/outbox", data)
|
||||||
|
|
||||||
|
assert json_response(conn, 400)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it rejects like activity to object invisible to actor", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
stranger = insert(:user, local: true)
|
||||||
|
{:ok, post} = CommonAPI.post(user, %{status: "cofe", visibility: "private"})
|
||||||
|
|
||||||
|
assert Pleroma.Web.ActivityPub.Visibility.private?(post)
|
||||||
|
refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(post, stranger)
|
||||||
|
|
||||||
|
post_object = Object.normalize(post, fetch: false)
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
type: "Like",
|
||||||
|
object: %{
|
||||||
|
id: post_object.data["id"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, stranger)
|
||||||
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|
|> post("/users/#{stranger.nickname}/outbox", data)
|
||||||
|
|
||||||
|
assert json_response(conn, 403)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it rejects announce activity to object invisible to actor", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
stranger = insert(:user, local: true)
|
||||||
|
{:ok, post} = CommonAPI.post(user, %{status: "cofe", visibility: "private"})
|
||||||
|
|
||||||
|
assert Pleroma.Web.ActivityPub.Visibility.private?(post)
|
||||||
|
refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(post, stranger)
|
||||||
|
|
||||||
|
post_object = Object.normalize(post, fetch: false)
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
type: "Announce",
|
||||||
|
object: %{
|
||||||
|
id: post_object.data["id"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, stranger)
|
||||||
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|
|> post("/users/#{stranger.nickname}/outbox", data)
|
||||||
|
|
||||||
|
assert json_response(conn, 403)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it rejects emojireact activity to object invisible to actor", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
stranger = insert(:user, local: true)
|
||||||
|
{:ok, post} = CommonAPI.post(user, %{status: "cofe", visibility: "private"})
|
||||||
|
|
||||||
|
assert Pleroma.Web.ActivityPub.Visibility.private?(post)
|
||||||
|
refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(post, stranger)
|
||||||
|
|
||||||
|
post_object = Object.normalize(post, fetch: false)
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
type: "EmojiReact",
|
||||||
|
object: %{
|
||||||
|
id: post_object.data["id"]
|
||||||
|
},
|
||||||
|
content: "😀"
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, stranger)
|
||||||
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|
|> post("/users/#{stranger.nickname}/outbox", data)
|
||||||
|
|
||||||
|
assert json_response(conn, 403)
|
||||||
|
end
|
||||||
|
|
||||||
test "it increases like count when receiving a like action", %{conn: conn} do
|
test "it increases like count when receiving a like action", %{conn: conn} do
|
||||||
note_activity = insert(:note_activity)
|
note_activity = insert(:note_activity)
|
||||||
note_object = Object.normalize(note_activity, fetch: false)
|
note_object = Object.normalize(note_activity, fetch: false)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,10 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.Builder
|
||||||
|
alias Pleroma.Web.ActivityPub.Pipeline
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
alias Pleroma.Web.ActivityPub.UserView
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.AdminAPI.AccountView
|
alias Pleroma.Web.AdminAPI.AccountView
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
@ -530,6 +533,9 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
||||||
assert is_nil(modified["object"]["announcements"])
|
assert is_nil(modified["object"]["announcements"])
|
||||||
assert is_nil(modified["object"]["announcement_count"])
|
assert is_nil(modified["object"]["announcement_count"])
|
||||||
assert is_nil(modified["object"]["generator"])
|
assert is_nil(modified["object"]["generator"])
|
||||||
|
assert is_nil(modified["object"]["rules"])
|
||||||
|
assert is_nil(modified["object"]["language"])
|
||||||
|
assert is_nil(modified["object"]["voters"])
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it strips internal fields of article" do
|
test "it strips internal fields of article" do
|
||||||
|
|
@ -587,6 +593,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
||||||
test "it can handle Listen activities" do
|
test "it can handle Listen activities" do
|
||||||
listen_activity = insert(:listen)
|
listen_activity = insert(:listen)
|
||||||
|
|
||||||
|
# This has an inlined object as in ObjectView
|
||||||
{:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
|
{:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
|
||||||
|
|
||||||
assert modified["type"] == "Listen"
|
assert modified["type"] == "Listen"
|
||||||
|
|
@ -595,7 +602,36 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
|
{:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
|
||||||
|
|
||||||
{:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data)
|
user_ap_id = user.ap_id
|
||||||
|
activity_ap_id = activity.data["id"]
|
||||||
|
activity_to = activity.data["to"]
|
||||||
|
activity_cc = activity.data["cc"]
|
||||||
|
object_ap_id = activity.data["object"]
|
||||||
|
object_type = activity.object.data["type"]
|
||||||
|
|
||||||
|
# This does not have an inlined object
|
||||||
|
{:ok, modified2} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
|
|
||||||
|
assert match?(
|
||||||
|
%{
|
||||||
|
"@context" => [_ | _],
|
||||||
|
"type" => "Listen",
|
||||||
|
"actor" => ^user_ap_id,
|
||||||
|
"to" => ^activity_to,
|
||||||
|
"cc" => ^activity_cc,
|
||||||
|
"context" => "http://localhost" <> _,
|
||||||
|
"id" => ^activity_ap_id,
|
||||||
|
"object" => %{
|
||||||
|
"actor" => ^user_ap_id,
|
||||||
|
"attributedTo" => ^user_ap_id,
|
||||||
|
"id" => ^object_ap_id,
|
||||||
|
"type" => ^object_type,
|
||||||
|
"to" => ^activity_to,
|
||||||
|
"cc" => ^activity_cc
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modified2
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "custom emoji urls are URI encoded" do
|
test "custom emoji urls are URI encoded" do
|
||||||
|
|
@ -635,6 +671,94 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
||||||
} = prepared["object"]
|
} = prepared["object"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "Updates of Actors are handled" do
|
||||||
|
user = insert(:user, local: true)
|
||||||
|
|
||||||
|
changeset = User.update_changeset(user, %{name: "new name"})
|
||||||
|
{:ok, unpersisted_user} = Ecto.Changeset.apply_action(changeset, :update)
|
||||||
|
|
||||||
|
updated_object =
|
||||||
|
UserView.render("user.json", user: unpersisted_user)
|
||||||
|
|> Map.delete("@context")
|
||||||
|
|
||||||
|
{:ok, update_data, []} = Builder.update(user, updated_object)
|
||||||
|
|
||||||
|
{:ok, activity, _} =
|
||||||
|
Pipeline.common_pipeline(update_data,
|
||||||
|
local: true,
|
||||||
|
user_update_changeset: changeset
|
||||||
|
)
|
||||||
|
|
||||||
|
assert {:ok, prepared} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
|
assert prepared["type"] == "Update"
|
||||||
|
assert prepared["@context"]
|
||||||
|
assert prepared["object"]["type"] == user.actor_type
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Correctly handles Undo activities" do
|
||||||
|
blocked = insert(:user)
|
||||||
|
blocker = insert(:user, local: true)
|
||||||
|
|
||||||
|
blocked_ap_id = blocked.ap_id
|
||||||
|
blocker_ap_id = blocker.ap_id
|
||||||
|
|
||||||
|
{:ok, %Activity{} = block_activity} = CommonAPI.block(blocked, blocker)
|
||||||
|
{:ok, %Activity{} = undo_activity} = CommonAPI.unblock(blocked, blocker)
|
||||||
|
{:ok, data} = Transmogrifier.prepare_outgoing(undo_activity.data)
|
||||||
|
|
||||||
|
block_ap_id = block_activity.data["id"]
|
||||||
|
assert is_binary(block_ap_id)
|
||||||
|
|
||||||
|
assert match?(
|
||||||
|
%{
|
||||||
|
"@context" => [_ | _],
|
||||||
|
"type" => "Undo",
|
||||||
|
"id" => "http://localhost" <> _,
|
||||||
|
"actor" => ^blocker_ap_id,
|
||||||
|
"object" => ^block_ap_id,
|
||||||
|
"to" => [^blocked_ap_id],
|
||||||
|
"cc" => [],
|
||||||
|
"bto" => [],
|
||||||
|
"bcc" => []
|
||||||
|
},
|
||||||
|
data
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Correctly handles EmojiReact activities" do
|
||||||
|
user = insert(:user, local: true)
|
||||||
|
note_activity = insert(:note_activity)
|
||||||
|
|
||||||
|
user_ap_id = user.ap_id
|
||||||
|
user_followers = user.follower_address
|
||||||
|
note_author = note_activity.data["actor"]
|
||||||
|
note_ap_id = note_activity.data["object"]
|
||||||
|
|
||||||
|
assert is_binary(note_author)
|
||||||
|
assert is_binary(note_ap_id)
|
||||||
|
|
||||||
|
{:ok, react_activity} = CommonAPI.react_with_emoji(note_activity.id, user, "🐈")
|
||||||
|
{:ok, data} = Transmogrifier.prepare_outgoing(react_activity.data)
|
||||||
|
|
||||||
|
assert match?(
|
||||||
|
%{
|
||||||
|
"@context" => [_ | _],
|
||||||
|
"type" => "EmojiReact",
|
||||||
|
"actor" => ^user_ap_id,
|
||||||
|
"to" => [^user_followers, ^note_author],
|
||||||
|
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"bto" => [],
|
||||||
|
"bcc" => [],
|
||||||
|
"content" => "🐈",
|
||||||
|
"context" => "2hu",
|
||||||
|
"id" => "http://localhost" <> _,
|
||||||
|
"object" => ^note_ap_id,
|
||||||
|
"tag" => []
|
||||||
|
},
|
||||||
|
data
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
test "it prepares a quote post" do
|
test "it prepares a quote post" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,4 +95,23 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do
|
||||||
assert result["object"] == announce.data["id"]
|
assert result["object"] == announce.data["id"]
|
||||||
assert result["type"] == "Undo"
|
assert result["type"] == "Undo"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "renders a listen activity" do
|
||||||
|
audio = insert(:audio)
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, listen_activity} = CommonAPI.listen(user, audio.data)
|
||||||
|
|
||||||
|
result = ObjectView.render("object.json", %{object: listen_activity})
|
||||||
|
|
||||||
|
assert result["id"] == listen_activity.data["id"]
|
||||||
|
assert result["to"] == listen_activity.data["to"]
|
||||||
|
assert result["type"] == "Listen"
|
||||||
|
assert result["object"]["album"] == listen_activity.data["album"]
|
||||||
|
assert result["object"]["artist"] == listen_activity.data["artist"]
|
||||||
|
assert result["object"]["length"] == listen_activity.data["length"]
|
||||||
|
assert result["object"]["title"] == listen_activity.data["title"]
|
||||||
|
assert result["object"]["type"] == "Audio"
|
||||||
|
assert result["@context"]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1086,7 +1086,7 @@ defmodule Pleroma.Web.CommonAPITest do
|
||||||
|
|
||||||
test "only public can be pinned", %{user: user} do
|
test "only public can be pinned", %{user: user} do
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "private status", visibility: "private"})
|
{:ok, activity} = CommonAPI.post(user, %{status: "private status", visibility: "private"})
|
||||||
{:error, :visibility_error} = CommonAPI.pin(activity.id, user)
|
{:error, :non_public_error} = CommonAPI.pin(activity.id, user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "unpin status", %{user: user, activity: activity} do
|
test "unpin status", %{user: user, activity: activity} do
|
||||||
|
|
@ -1300,6 +1300,47 @@ defmodule Pleroma.Web.CommonAPITest do
|
||||||
} = flag_activity
|
} = flag_activity
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "doesn't create a report when post is not visible to user" do
|
||||||
|
reporter = insert(:user)
|
||||||
|
target_user = insert(:user)
|
||||||
|
{:ok, post} = CommonAPI.post(target_user, %{status: "Eric", visibility: "private"})
|
||||||
|
|
||||||
|
assert Pleroma.Web.ActivityPub.Visibility.private?(post)
|
||||||
|
refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(post, reporter)
|
||||||
|
|
||||||
|
# Fails when all status are invisible
|
||||||
|
report_data = %{
|
||||||
|
account_id: target_user.id,
|
||||||
|
comment: "foobar",
|
||||||
|
status_ids: [post.id]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:error, :visibility_error} = CommonAPI.report(reporter, report_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "doesn't create a report when some posts are not visible to user" do
|
||||||
|
reporter = insert(:user)
|
||||||
|
target_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, visible_activity} = CommonAPI.post(target_user, %{status: "cofe"})
|
||||||
|
|
||||||
|
{:ok, invisibile_activity} =
|
||||||
|
CommonAPI.post(target_user, %{status: "cawfee", visibility: "private"})
|
||||||
|
|
||||||
|
assert Pleroma.Web.ActivityPub.Visibility.private?(invisibile_activity)
|
||||||
|
assert Pleroma.Web.ActivityPub.Visibility.public?(visible_activity)
|
||||||
|
refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(invisibile_activity, reporter)
|
||||||
|
|
||||||
|
# Fails when some statuses are invisible
|
||||||
|
report_data_partial = %{
|
||||||
|
account_id: target_user.id,
|
||||||
|
comment: "foobar",
|
||||||
|
status_ids: [visible_activity.id, invisibile_activity.id]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:error, :visibility_error} = CommonAPI.report(reporter, report_data_partial)
|
||||||
|
end
|
||||||
|
|
||||||
test "updates report state" do
|
test "updates report state" do
|
||||||
[reporter, target_user] = insert_pair(:user)
|
[reporter, target_user] = insert_pair(:user)
|
||||||
activity = insert(:note_activity, user: target_user)
|
activity = insert(:note_activity, user: target_user)
|
||||||
|
|
|
||||||
|
|
@ -316,6 +316,9 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
%{user: other_user, conn: conn} = oauth_access(["read:notifications"])
|
%{user: other_user, conn: conn} = oauth_access(["read:notifications"])
|
||||||
|
|
||||||
|
{:ok, _, _, %{data: %{"state" => "accept"}}} = CommonAPI.follow(other_user, user)
|
||||||
|
{:ok, _, _, %{data: %{"state" => "accept"}}} = CommonAPI.follow(user, other_user)
|
||||||
|
|
||||||
{:ok, public_activity} = CommonAPI.post(other_user, %{status: ".", visibility: "public"})
|
{:ok, public_activity} = CommonAPI.post(other_user, %{status: ".", visibility: "public"})
|
||||||
|
|
||||||
{:ok, direct_activity} =
|
{:ok, direct_activity} =
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do
|
||||||
|> json_response_and_validate_schema(400)
|
|> json_response_and_validate_schema(400)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns error when account is not exist", %{
|
test "returns error when account does not exist", %{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
activity: activity
|
activity: activity
|
||||||
} do
|
} do
|
||||||
|
|
@ -159,6 +159,51 @@ defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do
|
||||||
assert json_response_and_validate_schema(conn, 400) == %{"error" => "Account not found"}
|
assert json_response_and_validate_schema(conn, 400) == %{"error" => "Account not found"}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "returns not found when post isn't visible to reporter", %{user: target_user} do
|
||||||
|
%{conn: conn, user: reporter} = oauth_access(["write:reports"])
|
||||||
|
|
||||||
|
{:ok, invisible_activity} =
|
||||||
|
CommonAPI.post(target_user, %{status: "Invisible!", visibility: "private"})
|
||||||
|
|
||||||
|
assert Pleroma.Web.ActivityPub.Visibility.private?(invisible_activity)
|
||||||
|
refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(invisible_activity, reporter)
|
||||||
|
|
||||||
|
assert %{"error" => "Record not found"} =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post(
|
||||||
|
"/api/v1/reports",
|
||||||
|
%{"account_id" => target_user.id, "status_ids" => [invisible_activity.id]}
|
||||||
|
)
|
||||||
|
|> json_response_and_validate_schema(404)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns not found when some post aren't visible to reporter", %{
|
||||||
|
activity: activity,
|
||||||
|
user: target_user
|
||||||
|
} do
|
||||||
|
%{conn: conn, user: reporter} = oauth_access(["write:reports"])
|
||||||
|
|
||||||
|
{:ok, invisible_activity} =
|
||||||
|
CommonAPI.post(target_user, %{status: "Invisible!", visibility: "private"})
|
||||||
|
|
||||||
|
assert Pleroma.Web.ActivityPub.Visibility.private?(invisible_activity)
|
||||||
|
assert Pleroma.Web.ActivityPub.Visibility.visible_for_user?(activity, reporter)
|
||||||
|
refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(invisible_activity, reporter)
|
||||||
|
|
||||||
|
assert %{"error" => "Record not found"} =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post(
|
||||||
|
"/api/v1/reports",
|
||||||
|
%{
|
||||||
|
"account_id" => target_user.id,
|
||||||
|
"status_ids" => [activity.id, invisible_activity.id]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> json_response_and_validate_schema(404)
|
||||||
|
end
|
||||||
|
|
||||||
test "doesn't fail if an admin has no email", %{conn: conn, target_user: target_user} do
|
test "doesn't fail if an admin has no email", %{conn: conn, target_user: target_user} do
|
||||||
insert(:user, %{is_admin: true, email: nil})
|
insert(:user, %{is_admin: true, email: nil})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Workers.ScheduledActivityWorker
|
alias Pleroma.Workers.ScheduledActivityWorker
|
||||||
|
|
||||||
|
|
@ -267,6 +268,73 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "replying to a post the current user can't access fails", %{user: user, conn: conn} do
|
||||||
|
stranger = insert(:user)
|
||||||
|
|
||||||
|
{:ok, priv_post_act} =
|
||||||
|
CommonAPI.post(stranger, %{status: "forbidden knowledge", visibility: "private"})
|
||||||
|
|
||||||
|
assert Visibility.visible_for_user?(priv_post_act, stranger)
|
||||||
|
refute Visibility.visible_for_user?(priv_post_act, user)
|
||||||
|
|
||||||
|
resp =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses", %{
|
||||||
|
"status" => "@#{stranger.nickname} :peek:",
|
||||||
|
"in_reply_to_id" => priv_post_act.id,
|
||||||
|
"visibility" => "private"
|
||||||
|
})
|
||||||
|
|> json_response_and_validate_schema(422)
|
||||||
|
|
||||||
|
assert match?(%{"error" => _}, resp)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "replying to own DM succeeds", %{user: user, conn: conn} do
|
||||||
|
# this is an "edge" case for visibility: replying user is not
|
||||||
|
# part of addressed users (but is the author)
|
||||||
|
stranger = insert(:user)
|
||||||
|
|
||||||
|
{:ok, %{id: dm_id} = dm_post_act} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
status: "@#{stranger.nickname} wanna lose your mind to forbidden knowledge?",
|
||||||
|
visibility: "direct"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert Visibility.visible_for_user?(dm_post_act, stranger)
|
||||||
|
assert Visibility.visible_for_user?(dm_post_act, user)
|
||||||
|
|
||||||
|
resp =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses", %{
|
||||||
|
"status" => "@#{stranger.nickname} :peek:",
|
||||||
|
"in_reply_to_id" => dm_id,
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert match?(%{"in_reply_to_id" => ^dm_id}, resp)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "replying to a non-post activity fails", %{conn: conn, user: user} do
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
|
||||||
|
assert Visibility.visible_for_user?(follow_activity, user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses", %{
|
||||||
|
"status" => "hiiii!",
|
||||||
|
"in_reply_to_id" => to_string(follow_activity.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"error" => "Can only reply to posts, not \"Follow\" activities"} =
|
||||||
|
json_response_and_validate_schema(conn, 422)
|
||||||
|
end
|
||||||
|
|
||||||
test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
|
test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|
|
@ -1416,6 +1484,24 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
||||||
|
|
||||||
assert to_string(activity.id) == id
|
assert to_string(activity.id) == id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "cannot reblog private status of others (even if visible)", %{conn: conn, user: user} do
|
||||||
|
followed = insert(:user, local: true)
|
||||||
|
|
||||||
|
{:ok, _, _, %{data: %{"state" => "accept"}}} = CommonAPI.follow(followed, user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(followed, %{status: "cofe", visibility: "private"})
|
||||||
|
|
||||||
|
assert Visibility.visible_for_user?(activity, user)
|
||||||
|
|
||||||
|
resp =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/#{activity.id}/reblog")
|
||||||
|
|> json_response_and_validate_schema(404)
|
||||||
|
|
||||||
|
assert match?(%{"error" => _}, resp)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "unreblogging" do
|
describe "unreblogging" do
|
||||||
|
|
@ -1445,6 +1531,34 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
||||||
|
|
||||||
assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
|
assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "can't unreblog someone else's reblog", %{user: user, conn: conn} do
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, %{id: reblog_id}} = CommonAPI.repeat(activity.id, other_user)
|
||||||
|
|
||||||
|
# unreblog by base post
|
||||||
|
resp1 =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/#{activity.id}/unreblog")
|
||||||
|
|> json_response(400)
|
||||||
|
|
||||||
|
assert match?(%{"error" => _}, resp1)
|
||||||
|
|
||||||
|
# unreblog by reblog ID (reblog IDs are accepted by some APIs;
|
||||||
|
# ensure it fails here one way or another)
|
||||||
|
resp2 =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> assign(:token, insert(:oauth_token, user: user, scopes: ["write", "read"]))
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/#{reblog_id}/unreblog")
|
||||||
|
|> json_response_and_validate_schema(404)
|
||||||
|
|
||||||
|
assert match?(%{"error" => _}, resp2)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "favoriting" do
|
describe "favoriting" do
|
||||||
|
|
@ -1477,13 +1591,23 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
||||||
|> json_response_and_validate_schema(200)
|
|> json_response_and_validate_schema(200)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns 404 error for a wrong id", %{conn: conn} do
|
test "a status you cannot see fails", %{conn: conn} do
|
||||||
conn =
|
stranger = insert(:user)
|
||||||
conn
|
|
||||||
|> put_req_header("content-type", "application/json")
|
|
||||||
|> post("/api/v1/statuses/1/favourite")
|
|
||||||
|
|
||||||
assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(stranger, %{status: "it can eternal lie", visibility: "private"})
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/#{activity.id}/favourite")
|
||||||
|
|> json_response_and_validate_schema(404) == %{"error" => "Record not found"}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns 404 error for a wrong id", %{conn: conn} do
|
||||||
|
assert conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/1/favourite")
|
||||||
|
|> json_response_and_validate_schema(404) == %{"error" => "Record not found"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -1506,6 +1630,54 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
||||||
assert to_string(activity.id) == id
|
assert to_string(activity.id) == id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "can't unfavourite post that isn't visible to user" do
|
||||||
|
user = insert(:user)
|
||||||
|
%{conn: conn, user: stranger} = oauth_access(["write:favourites"])
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "invisible", visibility: "private"})
|
||||||
|
|
||||||
|
refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(activity, stranger)
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/#{activity.id}/unfavourite")
|
||||||
|
|> json_response_and_validate_schema(404) == %{"error" => "Record not found"}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can't unfavourite post that isn't favourited", %{conn: conn} do
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
|
||||||
|
# using base post ID
|
||||||
|
assert conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/#{activity.id}/unfavourite")
|
||||||
|
|> json_response_and_validate_schema(400) == %{"error" => "Could not unfavorite"}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can't unfavourite other user's favs", %{conn: conn} do
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
|
||||||
|
other = insert(:user)
|
||||||
|
{:ok, _} = CommonAPI.favorite(activity.id, other)
|
||||||
|
|
||||||
|
# using base post ID
|
||||||
|
assert conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/#{activity.id}/unfavourite")
|
||||||
|
|> json_response_and_validate_schema(400) == %{"error" => "Could not unfavorite"}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can't unfavourite other user's favs using their activity", %{conn: conn} do
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
|
||||||
|
other = insert(:user)
|
||||||
|
{:ok, fav_activity} = CommonAPI.favorite(activity.id, other)
|
||||||
|
# some APIs (used to) take IDs of any activity type, make sure this fails one way or another
|
||||||
|
assert conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/#{fav_activity.id}/unfavourite")
|
||||||
|
|> json_response_and_validate_schema(404) == %{"error" => "Record not found"}
|
||||||
|
end
|
||||||
|
|
||||||
test "returns 404 error for a wrong id", %{conn: conn} do
|
test "returns 404 error for a wrong id", %{conn: conn} do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|
|
@ -1516,6 +1688,19 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "can't favourite post that isn't visible to user" do
|
||||||
|
user = insert(:user)
|
||||||
|
%{conn: conn, user: stranger} = oauth_access(["write:favourites"])
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "invisible", visibility: "private"})
|
||||||
|
|
||||||
|
refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(activity, stranger)
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/#{activity.id}/favourite")
|
||||||
|
|> json_response_and_validate_schema(404) == %{"error" => "Record not found"}
|
||||||
|
end
|
||||||
|
|
||||||
describe "pinned statuses" do
|
describe "pinned statuses" do
|
||||||
setup do: oauth_access(["write:accounts"])
|
setup do: oauth_access(["write:accounts"])
|
||||||
|
|
||||||
|
|
@ -1549,7 +1734,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
||||||
|> json_response(403) == %{"error" => "Invalid credentials."}
|
|> json_response(403) == %{"error" => "Invalid credentials."}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
|
test "/pin: returns 422 error when activity is not public", %{conn: conn, user: user} do
|
||||||
{:ok, dm} = CommonAPI.post(user, %{status: "test", visibility: "direct"})
|
{:ok, dm} = CommonAPI.post(user, %{status: "test", visibility: "direct"})
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
|
|
@ -1562,6 +1747,18 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "/pin: returns 404 error when activity not visible to user", %{user: user} do
|
||||||
|
%{conn: conn, user: stranger} = oauth_access(["write:accounts"])
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "invisible", visibility: "private"})
|
||||||
|
|
||||||
|
refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(activity, stranger)
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/#{activity.id}/pin")
|
||||||
|
|> json_response_and_validate_schema(404) == %{"error" => "Record not found"}
|
||||||
|
end
|
||||||
|
|
||||||
test "pin by another user", %{activity: activity} do
|
test "pin by another user", %{activity: activity} do
|
||||||
%{conn: conn} = oauth_access(["write:accounts"])
|
%{conn: conn} = oauth_access(["write:accounts"])
|
||||||
|
|
||||||
|
|
@ -1596,6 +1793,32 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
||||||
|> json_response_and_validate_schema(404) == %{"error" => "Record not found"}
|
|> json_response_and_validate_schema(404) == %{"error" => "Record not found"}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "/unpin: returns 404 error when activity not visible to user", %{user: user} do
|
||||||
|
%{conn: conn, user: stranger} = oauth_access(["write:accounts"])
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "yumi", visibility: "private"})
|
||||||
|
|
||||||
|
refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(activity, stranger)
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> assign(:user, stranger)
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/#{activity.id}/unpin")
|
||||||
|
|> json_response_and_validate_schema(404) == %{"error" => "Record not found"}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "/unpin: returns 422 error when activity not owned by user", %{activity: activity} do
|
||||||
|
%{conn: conn, user: user} = oauth_access(["write:accounts"])
|
||||||
|
|
||||||
|
assert Pleroma.Web.ActivityPub.Visibility.visible_for_user?(activity, user)
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/#{activity.id}/unpin")
|
||||||
|
|> json_response_and_validate_schema(422) == %{
|
||||||
|
"error" => "Someone else's status cannot be unpinned"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
|
test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
|
||||||
{:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
|
{:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
|
||||||
|
|
||||||
|
|
@ -1707,6 +1930,28 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
||||||
json_response_and_validate_schema(bookmarks, 200)
|
json_response_and_validate_schema(bookmarks, 200)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "cannot bookmark invisible post" do
|
||||||
|
user = insert(:user)
|
||||||
|
%{conn: conn, user: stranger} = oauth_access(["write:bookmarks"])
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "mocha", visibility: "private"})
|
||||||
|
|
||||||
|
refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(activity, stranger)
|
||||||
|
|
||||||
|
resp1 =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/#{activity.id}/bookmark")
|
||||||
|
|
||||||
|
assert json_response_and_validate_schema(resp1, 404) == %{"error" => "Record not found"}
|
||||||
|
|
||||||
|
resp2 =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/#{activity.id}/unbookmark")
|
||||||
|
|
||||||
|
assert json_response_and_validate_schema(resp2, 404) == %{"error" => "Record not found"}
|
||||||
|
end
|
||||||
|
|
||||||
test "bookmark folders" do
|
test "bookmark folders" do
|
||||||
%{conn: conn, user: user} = oauth_access(["write:bookmarks", "read:bookmarks"])
|
%{conn: conn, user: user} = oauth_access(["write:bookmarks", "read:bookmarks"])
|
||||||
|
|
||||||
|
|
@ -1804,6 +2049,30 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
||||||
|> post("/api/v1/statuses/#{activity.id}/unmute")
|
|> post("/api/v1/statuses/#{activity.id}/unmute")
|
||||||
|> json_response_and_validate_schema(200)
|
|> json_response_and_validate_schema(200)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "cannot mute not visible conversation", %{user: user} do
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "Invisible!", visibility: "private"})
|
||||||
|
%{conn: conn} = oauth_access(["write:mutes"])
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/#{activity.id}/mute")
|
||||||
|
|> json_response_and_validate_schema(404) == %{
|
||||||
|
"error" => "Record not found"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cannot unmute not visible conversation", %{user: user} do
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "Invisible!", visibility: "private"})
|
||||||
|
%{conn: conn} = oauth_access(["write:mutes"])
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/#{activity.id}/unmute")
|
||||||
|
|> json_response_and_validate_schema(404) == %{
|
||||||
|
"error" => "Record not found"
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
|
test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
|
||||||
|
|
@ -1970,6 +2239,22 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
||||||
assert id == other_user.id
|
assert id == other_user.id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "fails when base post not visible to current user", %{user: user} do
|
||||||
|
other_user = insert(:user, local: true)
|
||||||
|
%{conn: conn} = oauth_access(["read:accounts"])
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
status: "craving tea and mochi rn",
|
||||||
|
visibility: "private"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> assign(:user, other_user)
|
||||||
|
|> get("/api/v1/statuses/#{activity.id}/favourited_by")
|
||||||
|
|> json_response_and_validate_schema(404) == %{"error" => "Record not found"}
|
||||||
|
end
|
||||||
|
|
||||||
test "returns empty array when :show_reactions is disabled", %{conn: conn, activity: activity} do
|
test "returns empty array when :show_reactions is disabled", %{conn: conn, activity: activity} do
|
||||||
clear_config([:instance, :show_reactions], false)
|
clear_config([:instance, :show_reactions], false)
|
||||||
|
|
||||||
|
|
@ -2088,6 +2373,25 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
||||||
|
|
||||||
assert [] == response
|
assert [] == response
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "does fail when requesting for a non-visible status", %{user: user} do
|
||||||
|
other_user = insert(:user, local: true)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
status: "deep below it sleeps and mustn't wake",
|
||||||
|
visibility: "private"
|
||||||
|
})
|
||||||
|
|
||||||
|
response =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, other_user)
|
||||||
|
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read"]))
|
||||||
|
|> get("/api/v1/statuses/#{activity.id}/reblogged_by")
|
||||||
|
|> json_response_and_validate_schema(404)
|
||||||
|
|
||||||
|
assert match?(%{"error" => _}, response)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "context" do
|
test "context" do
|
||||||
|
|
@ -2110,6 +2414,34 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
||||||
} = response
|
} = response
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "context doesn't leak priv posts" do
|
||||||
|
%{user: user, conn: conn} = oauth_access(["read:statuses"])
|
||||||
|
stranger = insert(:user)
|
||||||
|
|
||||||
|
{:ok, %{id: id1}} = CommonAPI.post(stranger, %{status: "1", visibility: "public"})
|
||||||
|
|
||||||
|
{:ok, %{id: id2}} =
|
||||||
|
CommonAPI.post(stranger, %{status: "2", visibility: "unlisted", in_reply_to_status_id: id1})
|
||||||
|
|
||||||
|
{:ok, %{id: _id_boo} = act_boo} =
|
||||||
|
CommonAPI.post(stranger, %{status: "boo", visibility: "private", in_reply_to_status_id: id1})
|
||||||
|
|
||||||
|
refute Visibility.visible_for_user?(act_boo, user)
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/statuses/#{id1}/context")
|
||||||
|
|> json_response_and_validate_schema(:ok)
|
||||||
|
|
||||||
|
assert match?(
|
||||||
|
%{
|
||||||
|
"ancestors" => [],
|
||||||
|
"descendants" => [%{"id" => ^id2}]
|
||||||
|
},
|
||||||
|
response
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
test "favorites paginate correctly" do
|
test "favorites paginate correctly" do
|
||||||
%{user: user, conn: conn} = oauth_access(["read:favourites"])
|
%{user: user, conn: conn} = oauth_access(["read:favourites"])
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,38 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Tests.ObanHelpers
|
alias Pleroma.Tests.ObanHelpers
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
defp prepare_reacted_post(visibility \\ "private") do
|
||||||
|
unrelated_user = insert(:user, local: true)
|
||||||
|
poster = insert(:user, local: true)
|
||||||
|
follower = insert(:user, local: true)
|
||||||
|
{:ok, _, _, %{data: %{"state" => "accept"}}} = CommonAPI.follow(poster, follower)
|
||||||
|
|
||||||
|
{:ok, post_activity} = CommonAPI.post(poster, %{status: "miaow!", visibility: visibility})
|
||||||
|
|
||||||
|
if visibility != "direct" do
|
||||||
|
assert Visibility.visible_for_user?(post_activity, follower)
|
||||||
|
end
|
||||||
|
|
||||||
|
if visibility in ["direct", "private"] do
|
||||||
|
refute Visibility.visible_for_user?(post_activity, unrelated_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, _react_activity} = CommonAPI.react_with_emoji(post_activity.id, follower, "🐾")
|
||||||
|
|
||||||
|
{post_activity, poster, follower, unrelated_user}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp prepare_conn_of_user(conn, user) do
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> assign(:token, insert(:oauth_token, user: user, scopes: ["write", "read"]))
|
||||||
|
end
|
||||||
|
|
||||||
setup do
|
setup do
|
||||||
Mox.stub_with(Pleroma.UnstubbedConfigMock, Pleroma.Test.StaticConfig)
|
Mox.stub_with(Pleroma.UnstubbedConfigMock, Pleroma.Test.StaticConfig)
|
||||||
:ok
|
:ok
|
||||||
|
|
@ -137,6 +165,28 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do
|
||||||
|> json_response_and_validate_schema(400)
|
|> json_response_and_validate_schema(400)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "PUT /api/v1/pleroma/statuses/:id/reactions/:emoji not allowed for non-visible posts", %{
|
||||||
|
conn: conn
|
||||||
|
} do
|
||||||
|
{%{id: activity_id} = _activity, _author, follower, stranger} = prepare_reacted_post()
|
||||||
|
|
||||||
|
# Works for follower
|
||||||
|
resp =
|
||||||
|
prepare_conn_of_user(conn, follower)
|
||||||
|
|> put("/api/v1/pleroma/statuses/#{activity_id}/reactions/🐈")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert match?(%{"id" => ^activity_id}, resp)
|
||||||
|
|
||||||
|
# Fails for stranger
|
||||||
|
resp =
|
||||||
|
prepare_conn_of_user(conn, stranger)
|
||||||
|
|> put("/api/v1/pleroma/statuses/#{activity_id}/reactions/🐈")
|
||||||
|
|> json_response_and_validate_schema(404)
|
||||||
|
|
||||||
|
assert match?(%{"error" => "Record not found"}, resp)
|
||||||
|
end
|
||||||
|
|
||||||
test "DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
|
test "DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
@ -211,6 +261,26 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do
|
||||||
|> json_response(400)
|
|> json_response(400)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji only allows original reacter to revoke",
|
||||||
|
%{conn: conn} do
|
||||||
|
{%{id: activity_id} = _activity, author, follower, unrelated} = prepare_reacted_post("public")
|
||||||
|
|
||||||
|
# Works for original reacter
|
||||||
|
prepare_conn_of_user(conn, follower)
|
||||||
|
|> delete("/api/v1/pleroma/statuses/#{activity_id}/reactions/🐾")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
# Fails for anyone else
|
||||||
|
for u <- [author, unrelated] do
|
||||||
|
resp =
|
||||||
|
prepare_conn_of_user(conn, u)
|
||||||
|
|> delete("/api/v1/pleroma/statuses/#{activity_id}/reactions/🐾")
|
||||||
|
|> json_response(400)
|
||||||
|
|
||||||
|
assert match?(%{"error" => _}, resp)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
test "GET /api/v1/pleroma/statuses/:id/reactions", %{conn: conn} do
|
test "GET /api/v1/pleroma/statuses/:id/reactions", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
@ -324,6 +394,25 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do
|
||||||
assert [%{"name" => "🎅", "count" => 2}] = result
|
assert [%{"name" => "🎅", "count" => 2}] = result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "GET /api/v1/pleroma/statuses/:id/reactions not allowed for non-visible posts", %{
|
||||||
|
conn: conn
|
||||||
|
} do
|
||||||
|
{%{id: activity_id} = _activity, _author, follower, stranger} = prepare_reacted_post()
|
||||||
|
|
||||||
|
# Works for follower
|
||||||
|
resp =
|
||||||
|
prepare_conn_of_user(conn, follower)
|
||||||
|
|> get("/api/v1/pleroma/statuses/#{activity_id}/reactions")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert match?([%{"name" => _, "count" => _} | _], resp)
|
||||||
|
|
||||||
|
# Fails for stranger
|
||||||
|
assert prepare_conn_of_user(conn, stranger)
|
||||||
|
|> get("/api/v1/pleroma/statuses/#{activity_id}/reactions")
|
||||||
|
|> json_response_and_validate_schema(404) == %{"error" => "Record not found"}
|
||||||
|
end
|
||||||
|
|
||||||
test "GET /api/v1/pleroma/statuses/:id/reactions with :show_reactions disabled", %{conn: conn} do
|
test "GET /api/v1/pleroma/statuses/:id/reactions with :show_reactions disabled", %{conn: conn} do
|
||||||
clear_config([:instance, :show_reactions], false)
|
clear_config([:instance, :show_reactions], false)
|
||||||
|
|
||||||
|
|
@ -372,4 +461,20 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do
|
||||||
|
|
||||||
assert represented_user["id"] == other_user.id
|
assert represented_user["id"] == other_user.id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "GET /api/v1/pleroma/statuses/:id/reactions/:emoji not allowed for non-visible posts", %{
|
||||||
|
conn: conn
|
||||||
|
} do
|
||||||
|
{%{id: activity_id} = _activity, _author, follower, stranger} = prepare_reacted_post()
|
||||||
|
|
||||||
|
# Works for follower
|
||||||
|
assert prepare_conn_of_user(conn, follower)
|
||||||
|
|> get("/api/v1/pleroma/statuses/#{activity_id}/reactions/🐈")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
# Fails for stranger
|
||||||
|
assert prepare_conn_of_user(conn, stranger)
|
||||||
|
|> get("/api/v1/pleroma/statuses/#{activity_id}/reactions/🐈")
|
||||||
|
|> json_response_and_validate_schema(404) == %{"error" => "Record not found"}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue