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",
|
||||
"generator",
|
||||
"rules",
|
||||
"language"
|
||||
"language",
|
||||
"voters"
|
||||
]
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ defmodule Pleroma.Object do
|
|||
Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}")
|
||||
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.
|
||||
# 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
|
||||
cond do
|
||||
Keyword.get(options, :id_only) ->
|
||||
ap_id
|
||||
|
||||
Keyword.get(options, :fetch) ->
|
||||
case Fetcher.fetch_object_from_id(ap_id, options) do
|
||||
{:ok, object} -> object
|
||||
|
|
|
|||
|
|
@ -482,6 +482,42 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
{:ok, activity}
|
||||
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(
|
||||
%{assigns: %{user: %User{nickname: nickname, ap_id: actor} = user}} = conn,
|
||||
%{"nickname" => nickname} = params
|
||||
|
|
@ -493,6 +529,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
|> Map.put("actor", actor)
|
||||
|
||||
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),
|
||||
%Activity{data: activity_data} <- Activity.normalize(activity) do
|
||||
conn
|
||||
|
|
|
|||
|
|
@ -873,6 +873,27 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
{:ok, data}
|
||||
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
|
||||
object =
|
||||
object_id
|
||||
|
|
|
|||
|
|
@ -15,26 +15,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
|
|||
Map.merge(base, additional)
|
||||
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
|
||||
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header(activity.data)
|
||||
object_id = Object.normalize(activity, id_only: true)
|
||||
|
||||
additional =
|
||||
Transmogrifier.prepare_object(activity.data)
|
||||
|> Map.put("object", object_id)
|
||||
|
||||
Map.merge(base, additional)
|
||||
{:ok, ap_data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
ap_data
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do
|
|||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Account
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiNotFoundError
|
||||
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Status
|
||||
|
||||
|
|
@ -35,7 +36,8 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do
|
|||
security: [%{"oAuth" => ["read:statuses"]}],
|
||||
operationId: "EmojiReactionController.index",
|
||||
responses: %{
|
||||
200 => array_of_reactions_response()
|
||||
200 => array_of_reactions_response(),
|
||||
404 => Operation.response("Access denied", "application/json", ApiNotFoundError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
@ -54,7 +56,8 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do
|
|||
operationId: "EmojiReactionController.create",
|
||||
responses: %{
|
||||
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
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.ApiSpec.ReportOperation do
|
|||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Helpers
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiNotFoundError
|
||||
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
|
||||
|
||||
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),
|
||||
responses: %{
|
||||
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
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
|||
alias Pleroma.Web.ApiSpec.AccountOperation
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Account
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiNotFoundError
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Attachment
|
||||
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Emoji
|
||||
|
|
@ -177,6 +178,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
|||
parameters: [id_param()],
|
||||
responses: %{
|
||||
200 => status_response(),
|
||||
400 => Operation.response("Error", "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"
|
||||
}
|
||||
}),
|
||||
404 =>
|
||||
Operation.response("Not found", "application/json", %Schema{
|
||||
allOf: [ApiError],
|
||||
title: "Unprocessable Entity",
|
||||
example: %{
|
||||
"error" => "Record not found"
|
||||
404 => Operation.response("Not found", "application/json", ApiNotFoundError),
|
||||
422 =>
|
||||
Operation.response(
|
||||
"Unprocessable Entity",
|
||||
"application/json",
|
||||
%Schema{
|
||||
allOf: [ApiError],
|
||||
title: "Unprocessable Entity",
|
||||
example: %{
|
||||
"error" => "Someone else's status cannot be unpinned"
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
@ -275,7 +282,8 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
|||
}
|
||||
}),
|
||||
responses: %{
|
||||
200 => status_response()
|
||||
200 => status_response(),
|
||||
404 => Operation.response("Not found", "application/json", ApiNotFoundError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
@ -289,7 +297,8 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
|||
operationId: "StatusController.unbookmark",
|
||||
parameters: [id_param()],
|
||||
responses: %{
|
||||
200 => status_response()
|
||||
200 => status_response(),
|
||||
404 => Operation.response("Not found", "application/json", ApiNotFoundError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
@ -324,7 +333,8 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
|||
],
|
||||
responses: %{
|
||||
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
|
||||
|
|
@ -340,7 +350,8 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
|||
parameters: [id_param()],
|
||||
responses: %{
|
||||
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
|
||||
|
|
|
|||
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
|
||||
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, %Activity{} = activity, _meta}} <-
|
||||
{:common_pipeline,
|
||||
|
|
@ -278,6 +279,9 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
{:find_object, _} ->
|
||||
{:error, :not_found}
|
||||
|
||||
{:visibility_error, _} ->
|
||||
{:error, :not_found}
|
||||
|
||||
{:common_pipeline, {:error, {:validate, {:error, changeset}}}} = e ->
|
||||
if {:object, {"already liked by this actor", []}} in changeset.errors do
|
||||
{:ok, :already_liked}
|
||||
|
|
@ -296,6 +300,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
|
||||
{:find_activity, Activity.get_by_id(id)},
|
||||
%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),
|
||||
{_, {:ok, _}} <- {:cancel_jobs, maybe_cancel_jobs(like)},
|
||||
{:ok, undo, _} <- Builder.undo(user, like),
|
||||
|
|
@ -303,6 +308,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
{:ok, activity}
|
||||
else
|
||||
{:find_activity, _} -> {:error, :not_found}
|
||||
{:visibility_error, _} -> {:error, :not_found}
|
||||
_ -> {:error, dgettext("errors", "Could not unfavorite")}
|
||||
end
|
||||
end
|
||||
|
|
@ -311,11 +317,15 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
{:ok, Activity.t()} | {:error, String.t()}
|
||||
def react_with_emoji(id, user, emoji) do
|
||||
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||
{_, true} <- {:visibility_error, activity_visible_to_actor(activity, user)},
|
||||
object <- Object.normalize(activity, fetch: false),
|
||||
{:ok, emoji_react, _} <- Builder.emoji_react(user, object, emoji),
|
||||
{:ok, activity, _} <- Pipeline.common_pipeline(emoji_react, local: true) do
|
||||
{:ok, activity}
|
||||
else
|
||||
{:visibility_error, _} ->
|
||||
{:error, :not_found}
|
||||
|
||||
_ ->
|
||||
{:error, dgettext("errors", "Could not add reaction emoji")}
|
||||
end
|
||||
|
|
@ -506,6 +516,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
@spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
|
||||
def pin(id, %User{} = user) do
|
||||
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 <- object_type_is_allowed_for_pin(activity.object),
|
||||
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(_, _), 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
|
||||
with false <- type in ["Note", "Article", "Question"] do
|
||||
{:error, :not_allowed}
|
||||
|
|
@ -539,13 +558,18 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
|
||||
defp activity_is_public(activity) do
|
||||
with false <- Visibility.public?(activity) do
|
||||
{:error, :visibility_error}
|
||||
{:error, :non_public_error}
|
||||
end
|
||||
end
|
||||
|
||||
@spec unpin(String.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
|
||||
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),
|
||||
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, _} <-
|
||||
Pipeline.common_pipeline(unpin_data,
|
||||
|
|
@ -562,7 +586,8 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
def add_mute(activity, user, params \\ %{}) do
|
||||
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
|
||||
if expires_in > 0 do
|
||||
Pleroma.Workers.MuteExpireWorker.new(
|
||||
|
|
@ -574,14 +599,21 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
|
||||
{:ok, activity}
|
||||
else
|
||||
{:error, :visibility_error} -> {:error, :visibility_error}
|
||||
{:error, _} -> {:error, dgettext("errors", "conversation is already muted")}
|
||||
end
|
||||
end
|
||||
|
||||
@spec remove_mute(Activity.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
def remove_mute(%Activity{} = activity, %User{} = user) do
|
||||
ThreadMute.remove_mute(user.id, activity.data["context"])
|
||||
{:ok, activity}
|
||||
case activity_visible_to_actor(activity, user) do
|
||||
true ->
|
||||
ThreadMute.remove_mute(user.id, activity.data["context"])
|
||||
{:ok, activity}
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
@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),
|
||||
{:ok, {content_html, _, _}} <- make_report_content_html(data[:comment]),
|
||||
{:ok, statuses} <- get_report_statuses(account, data),
|
||||
true <- check_statuses_visibility(user, statuses),
|
||||
rules <- get_report_rules(Map.get(data, :rule_ids, nil)) do
|
||||
ActivityPub.flag(%{
|
||||
context: Utils.generate_context_id(),
|
||||
|
|
@ -622,9 +655,27 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
forward: Map.get(data, :forward, false),
|
||||
rules: rules
|
||||
})
|
||||
else
|
||||
false ->
|
||||
{:error, :visibility_error}
|
||||
|
||||
error ->
|
||||
error
|
||||
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
|
||||
case User.get_cached_by_id(account_id) do
|
||||
%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: :deleted}} = draft) do
|
||||
add_error(draft, dgettext("errors", "Cannot reply to a deleted status"))
|
||||
end
|
||||
defp in_reply_to(%{params: %{in_reply_to_status_id: id}} = draft) when is_binary(id) do
|
||||
# If a post was deleted all its activities (except the newly added Delete) are purged too,
|
||||
# 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
|
||||
activity = Activity.get_by_id(id)
|
||||
false ->
|
||||
add_error(draft, dgettext("errors", "Record not found"))
|
||||
|
||||
params =
|
||||
if is_nil(activity) do
|
||||
# Deleted activities are returned as nil
|
||||
Map.put(params, :in_reply_to_status_id, :deleted)
|
||||
else
|
||||
Map.put(params, :in_reply_to_status_id, activity)
|
||||
end
|
||||
|
||||
in_reply_to(%{draft | params: params})
|
||||
{:type, type} ->
|
||||
add_error(
|
||||
draft,
|
||||
dgettext("errors", "Can only reply to posts, not %{type} activities",
|
||||
type: inspect(type)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
with {:ok, activity} <- Pleroma.Web.CommonAPI.report(user, params) do
|
||||
render(conn, "show.json", activity: activity)
|
||||
else
|
||||
{:error, :visibility_error} ->
|
||||
{:error, :not_found, "Record not found"}
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -319,6 +319,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
@doc "DELETE /api/v1/statuses/:id"
|
||||
def delete(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do
|
||||
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
|
||||
try_render(conn, "show.json",
|
||||
activity: activity,
|
||||
|
|
@ -340,6 +341,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
_
|
||||
) do
|
||||
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
|
||||
try_render(conn, "show.json", %{activity: announce, for: user, as: :activity})
|
||||
end
|
||||
|
|
@ -364,6 +366,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
_
|
||||
) do
|
||||
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
|
||||
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||
end
|
||||
|
|
@ -390,6 +393,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do
|
||||
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, :pinned_statuses_limit_reached} ->
|
||||
{: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, :visibility_error} ->
|
||||
{:error, :not_found, "Record not found"}
|
||||
|
||||
{:error, :non_public_error} ->
|
||||
{:error, :unprocessable_entity, "Non-public status cannot be pinned"}
|
||||
|
||||
error ->
|
||||
|
|
@ -410,8 +418,20 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
conn,
|
||||
_
|
||||
) do
|
||||
# CommonAPI already checks whether user can unpin
|
||||
with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do
|
||||
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
|
||||
|
||||
|
|
@ -434,6 +454,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
),
|
||||
{:ok, _bookmark} <- Bookmark.create(user.id, activity.id, folder_id) do
|
||||
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||
else
|
||||
false ->
|
||||
{:error, :not_found, "Record not found"}
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -447,6 +473,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
true <- Visibility.visible_for_user?(activity, user),
|
||||
{:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do
|
||||
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||
else
|
||||
false ->
|
||||
{:error, :not_found, "Record not found"}
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -459,8 +491,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
_
|
||||
) do
|
||||
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
|
||||
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
|
||||
|
||||
|
|
@ -473,8 +512,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
_
|
||||
) do
|
||||
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
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
|
|||
alias Pleroma.Activity
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
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
|
||||
with true <- Pleroma.Config.get([:instance, :show_reactions]),
|
||||
%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),
|
||||
reactions <- Object.get_emoji_reactions(object) do
|
||||
reactions =
|
||||
|
|
@ -37,6 +39,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
|
|||
|
||||
render(conn, "index.json", emoji_reactions: reactions, user: user)
|
||||
else
|
||||
{:visible, _} -> {:error, :not_found}
|
||||
_e -> json(conn, [])
|
||||
end
|
||||
end
|
||||
|
|
@ -76,6 +79,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
|
|||
|> Pleroma.Emoji.fully_qualify_emoji()
|
||||
|> Pleroma.Emoji.maybe_quote()
|
||||
|
||||
# CommonAPI checks if allowed to react
|
||||
with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji) do
|
||||
activity = Activity.get_by_id(activity_id)
|
||||
|
||||
|
|
@ -91,6 +95,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
|
|||
|> Pleroma.Emoji.fully_qualify_emoji()
|
||||
|> Pleroma.Emoji.maybe_quote()
|
||||
|
||||
# CommonAPI checks only author can revoke reactions
|
||||
with {:ok, _activity} <- CommonAPI.unreact_with_emoji(activity_id, user, emoji) do
|
||||
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
|
||||
{:ok, _direct2} =
|
||||
CommonAPI.post(blocked, %{
|
||||
status: "reply",
|
||||
status: "@#{third_user.nickname}, #{blocker.nickname} reply",
|
||||
visibility: "direct",
|
||||
in_reply_to_conversation_id: blocked_participation.id
|
||||
})
|
||||
|
|
|
|||
|
|
@ -66,8 +66,10 @@ defmodule Pleroma.ConversationTest do
|
|||
jafnhar = insert(:user, local: false)
|
||||
tridi = insert(:user)
|
||||
|
||||
to = [har.nickname, jafnhar.nickname, tridi.nickname]
|
||||
|
||||
{: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)
|
||||
context = object.data["context"]
|
||||
|
|
@ -88,7 +90,8 @@ defmodule Pleroma.ConversationTest do
|
|||
CommonAPI.post(jafnhar, %{
|
||||
status: "Hey @#{har.nickname}",
|
||||
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)
|
||||
|
|
@ -112,7 +115,8 @@ defmodule Pleroma.ConversationTest do
|
|||
CommonAPI.post(tridi, %{
|
||||
status: "Hey @#{har.nickname}",
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1580,6 +1580,41 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
assert object["content"] == activity["object"]["content"]
|
||||
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
|
||||
user = insert(:user)
|
||||
|
||||
|
|
@ -1706,6 +1741,289 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
assert note_object == Object.normalize(note_activity, fetch: false)
|
||||
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
|
||||
note_activity = insert(:note_activity)
|
||||
note_object = Object.normalize(note_activity, fetch: false)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
|||
alias Pleroma.Activity
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.Pipeline
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.ActivityPub.UserView
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.AdminAPI.AccountView
|
||||
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"]["announcement_count"])
|
||||
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
|
||||
|
||||
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
|
||||
listen_activity = insert(:listen)
|
||||
|
||||
# This has an inlined object as in ObjectView
|
||||
{:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
|
||||
|
||||
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, _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
|
||||
|
||||
test "custom emoji urls are URI encoded" do
|
||||
|
|
@ -635,6 +671,94 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
|||
} = prepared["object"]
|
||||
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
|
||||
user = insert(:user)
|
||||
|
||||
|
|
|
|||
|
|
@ -95,4 +95,23 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do
|
|||
assert result["object"] == announce.data["id"]
|
||||
assert result["type"] == "Undo"
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1086,7 +1086,7 @@ defmodule Pleroma.Web.CommonAPITest do
|
|||
|
||||
test "only public can be pinned", %{user: user} do
|
||||
{: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
|
||||
|
||||
test "unpin status", %{user: user, activity: activity} do
|
||||
|
|
@ -1300,6 +1300,47 @@ defmodule Pleroma.Web.CommonAPITest do
|
|||
} = flag_activity
|
||||
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
|
||||
[reporter, target_user] = insert_pair(:user)
|
||||
activity = insert(:note_activity, user: target_user)
|
||||
|
|
|
|||
|
|
@ -316,6 +316,9 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
|
|||
user = insert(:user)
|
||||
%{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, direct_activity} =
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do
|
|||
|> json_response_and_validate_schema(400)
|
||||
end
|
||||
|
||||
test "returns error when account is not exist", %{
|
||||
test "returns error when account does not exist", %{
|
||||
conn: conn,
|
||||
activity: activity
|
||||
} do
|
||||
|
|
@ -159,6 +159,51 @@ defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do
|
|||
assert json_response_and_validate_schema(conn, 400) == %{"error" => "Account not found"}
|
||||
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
|
||||
insert(:user, %{is_admin: true, email: nil})
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Workers.ScheduledActivityWorker
|
||||
|
||||
|
|
@ -267,6 +268,73 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
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
|
||||
conn =
|
||||
conn
|
||||
|
|
@ -1416,6 +1484,24 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
|
||||
assert to_string(activity.id) == id
|
||||
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
|
||||
|
||||
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"}
|
||||
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
|
||||
|
||||
describe "favoriting" do
|
||||
|
|
@ -1477,13 +1591,23 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
|> json_response_and_validate_schema(200)
|
||||
end
|
||||
|
||||
test "returns 404 error for a wrong id", %{conn: conn} do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/statuses/1/favourite")
|
||||
test "a status you cannot see fails", %{conn: conn} do
|
||||
stranger = insert(:user)
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -1506,6 +1630,54 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
assert to_string(activity.id) == id
|
||||
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
|
||||
conn =
|
||||
conn
|
||||
|
|
@ -1516,6 +1688,19 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
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
|
||||
setup do: oauth_access(["write:accounts"])
|
||||
|
||||
|
|
@ -1549,7 +1734,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
|> json_response(403) == %{"error" => "Invalid credentials."}
|
||||
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"})
|
||||
|
||||
conn =
|
||||
|
|
@ -1562,6 +1747,18 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
}
|
||||
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
|
||||
%{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"}
|
||||
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
|
||||
{: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)
|
||||
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
|
||||
%{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")
|
||||
|> json_response_and_validate_schema(200)
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
clear_config([:instance, :show_reactions], false)
|
||||
|
||||
|
|
@ -2088,6 +2373,25 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
|
||||
assert [] == response
|
||||
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
|
||||
|
||||
test "context" do
|
||||
|
|
@ -2110,6 +2414,34 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
} = response
|
||||
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
|
||||
%{user: user, conn: conn} = oauth_access(["read:favourites"])
|
||||
other_user = insert(:user)
|
||||
|
|
|
|||
|
|
@ -9,10 +9,38 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do
|
|||
alias Pleroma.Object
|
||||
alias Pleroma.Tests.ObanHelpers
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
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
|
||||
Mox.stub_with(Pleroma.UnstubbedConfigMock, Pleroma.Test.StaticConfig)
|
||||
:ok
|
||||
|
|
@ -137,6 +165,28 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do
|
|||
|> json_response_and_validate_schema(400)
|
||||
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
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
|
@ -211,6 +261,26 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do
|
|||
|> json_response(400)
|
||||
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
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
|
@ -324,6 +394,25 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do
|
|||
assert [%{"name" => "🎅", "count" => 2}] = result
|
||||
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
|
||||
clear_config([:instance, :show_reactions], false)
|
||||
|
||||
|
|
@ -372,4 +461,20 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do
|
|||
|
||||
assert represented_user["id"] == other_user.id
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue