3cb471ec06
In the "pleroma" section of the MastoAPI for status activities you can see an expires_at item that states when the activity will expire, or nothing if the activity will not expire. The expires_at date is only visible to the person who posted the activity. This is the conservative approach in case some attacker decides to write a logger for expiring posts. However, in the future of OCAP, signed requests, and all that stuff, this attack might not be that likely. Some other pleroma dev should remove the restriction in the code at that time, if they're satisfied with the security implications of doing so.
558 lines
15 KiB
Elixir
558 lines
15 KiB
Elixir
# Pleroma: A lightweight social networking server
|
|
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
|
|
use Pleroma.DataCase
|
|
|
|
alias Pleroma.Activity
|
|
alias Pleroma.Bookmark
|
|
alias Pleroma.Object
|
|
alias Pleroma.Repo
|
|
alias Pleroma.User
|
|
alias Pleroma.Web.CommonAPI
|
|
alias Pleroma.Web.CommonAPI.Utils
|
|
alias Pleroma.Web.MastodonAPI.AccountView
|
|
alias Pleroma.Web.MastodonAPI.StatusView
|
|
alias Pleroma.Web.OStatus
|
|
import Pleroma.Factory
|
|
import Tesla.Mock
|
|
|
|
setup do
|
|
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
|
:ok
|
|
end
|
|
|
|
test "returns a temporary ap_id based user for activities missing db users" do
|
|
user = insert(:user)
|
|
|
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})
|
|
|
|
Repo.delete(user)
|
|
Cachex.clear(:user_cache)
|
|
|
|
%{account: ms_user} = StatusView.render("status.json", activity: activity)
|
|
|
|
assert ms_user.acct == "erroruser@example.com"
|
|
end
|
|
|
|
test "tries to get a user by nickname if fetching by ap_id doesn't work" do
|
|
user = insert(:user)
|
|
|
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})
|
|
|
|
{:ok, user} =
|
|
user
|
|
|> Ecto.Changeset.change(%{ap_id: "#{user.ap_id}/extension/#{user.nickname}"})
|
|
|> Repo.update()
|
|
|
|
Cachex.clear(:user_cache)
|
|
|
|
result = StatusView.render("status.json", activity: activity)
|
|
|
|
assert result[:account][:id] == to_string(user.id)
|
|
end
|
|
|
|
test "a note with null content" do
|
|
note = insert(:note_activity)
|
|
note_object = Object.normalize(note)
|
|
|
|
data =
|
|
note_object.data
|
|
|> Map.put("content", nil)
|
|
|
|
Object.change(note_object, %{data: data})
|
|
|> Object.update_and_set_cache()
|
|
|
|
User.get_cached_by_ap_id(note.data["actor"])
|
|
|
|
status = StatusView.render("status.json", %{activity: note})
|
|
|
|
assert status.content == ""
|
|
end
|
|
|
|
test "a note activity" do
|
|
note = insert(:note_activity)
|
|
object_data = Object.normalize(note).data
|
|
user = User.get_cached_by_ap_id(note.data["actor"])
|
|
|
|
convo_id = Utils.context_to_conversation_id(object_data["context"])
|
|
|
|
status = StatusView.render("status.json", %{activity: note})
|
|
|
|
created_at =
|
|
(object_data["published"] || "")
|
|
|> String.replace(~r/\.\d+Z/, ".000Z")
|
|
|
|
expected = %{
|
|
id: to_string(note.id),
|
|
uri: object_data["id"],
|
|
url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note),
|
|
account: AccountView.render("account.json", %{user: user}),
|
|
in_reply_to_id: nil,
|
|
in_reply_to_account_id: nil,
|
|
card: nil,
|
|
reblog: nil,
|
|
content: HtmlSanitizeEx.basic_html(object_data["content"]),
|
|
created_at: created_at,
|
|
reblogs_count: 0,
|
|
replies_count: 0,
|
|
favourites_count: 0,
|
|
reblogged: false,
|
|
bookmarked: false,
|
|
favourited: false,
|
|
muted: false,
|
|
pinned: false,
|
|
sensitive: false,
|
|
poll: nil,
|
|
spoiler_text: HtmlSanitizeEx.basic_html(object_data["summary"]),
|
|
visibility: "public",
|
|
media_attachments: [],
|
|
mentions: [],
|
|
tags: [
|
|
%{
|
|
name: "#{object_data["tag"]}",
|
|
url: "/tag/#{object_data["tag"]}"
|
|
}
|
|
],
|
|
application: %{
|
|
name: "Web",
|
|
website: nil
|
|
},
|
|
language: nil,
|
|
emojis: [
|
|
%{
|
|
shortcode: "2hu",
|
|
url: "corndog.png",
|
|
static_url: "corndog.png",
|
|
visible_in_picker: false
|
|
}
|
|
],
|
|
pleroma: %{
|
|
local: true,
|
|
conversation_id: convo_id,
|
|
in_reply_to_account_acct: nil,
|
|
content: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["content"])},
|
|
spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["summary"])},
|
|
expires_at: nil
|
|
}
|
|
}
|
|
|
|
assert status == expected
|
|
end
|
|
|
|
test "tells if the message is muted for some reason" do
|
|
user = insert(:user)
|
|
other_user = insert(:user)
|
|
|
|
{:ok, user} = User.mute(user, other_user)
|
|
|
|
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
|
|
status = StatusView.render("status.json", %{activity: activity})
|
|
|
|
assert status.muted == false
|
|
|
|
status = StatusView.render("status.json", %{activity: activity, for: user})
|
|
|
|
assert status.muted == true
|
|
end
|
|
|
|
test "tells if the status is bookmarked" do
|
|
user = insert(:user)
|
|
|
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "Cute girls doing cute things"})
|
|
status = StatusView.render("status.json", %{activity: activity})
|
|
|
|
assert status.bookmarked == false
|
|
|
|
status = StatusView.render("status.json", %{activity: activity, for: user})
|
|
|
|
assert status.bookmarked == false
|
|
|
|
{:ok, _bookmark} = Bookmark.create(user.id, activity.id)
|
|
|
|
activity = Activity.get_by_id_with_object(activity.id)
|
|
|
|
status = StatusView.render("status.json", %{activity: activity, for: user})
|
|
|
|
assert status.bookmarked == true
|
|
end
|
|
|
|
test "a reply" do
|
|
note = insert(:note_activity)
|
|
user = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(user, %{"status" => "he", "in_reply_to_status_id" => note.id})
|
|
|
|
status = StatusView.render("status.json", %{activity: activity})
|
|
|
|
assert status.in_reply_to_id == to_string(note.id)
|
|
|
|
[status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
|
|
|
|
assert status.in_reply_to_id == to_string(note.id)
|
|
end
|
|
|
|
test "contains mentions" do
|
|
incoming = File.read!("test/fixtures/incoming_reply_mastodon.xml")
|
|
# a user with this ap id might be in the cache.
|
|
recipient = "https://pleroma.soykaf.com/users/lain"
|
|
user = insert(:user, %{ap_id: recipient})
|
|
|
|
{:ok, [activity]} = OStatus.handle_incoming(incoming)
|
|
|
|
status = StatusView.render("status.json", %{activity: activity})
|
|
|
|
assert status.mentions ==
|
|
Enum.map([user], fn u -> AccountView.render("mention.json", %{user: u}) end)
|
|
end
|
|
|
|
test "create mentions from the 'to' field" do
|
|
%User{ap_id: recipient_ap_id} = insert(:user)
|
|
cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
|
|
|
|
object =
|
|
insert(:note, %{
|
|
data: %{
|
|
"to" => [recipient_ap_id],
|
|
"cc" => cc
|
|
}
|
|
})
|
|
|
|
activity =
|
|
insert(:note_activity, %{
|
|
note: object,
|
|
recipients: [recipient_ap_id | cc]
|
|
})
|
|
|
|
assert length(activity.recipients) == 3
|
|
|
|
%{mentions: [mention] = mentions} = StatusView.render("status.json", %{activity: activity})
|
|
|
|
assert length(mentions) == 1
|
|
assert mention.url == recipient_ap_id
|
|
end
|
|
|
|
test "create mentions from the 'tag' field" do
|
|
recipient = insert(:user)
|
|
cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
|
|
|
|
object =
|
|
insert(:note, %{
|
|
data: %{
|
|
"cc" => cc,
|
|
"tag" => [
|
|
%{
|
|
"href" => recipient.ap_id,
|
|
"name" => recipient.nickname,
|
|
"type" => "Mention"
|
|
},
|
|
%{
|
|
"href" => "https://example.com/search?tag=test",
|
|
"name" => "#test",
|
|
"type" => "Hashtag"
|
|
}
|
|
]
|
|
}
|
|
})
|
|
|
|
activity =
|
|
insert(:note_activity, %{
|
|
note: object,
|
|
recipients: [recipient.ap_id | cc]
|
|
})
|
|
|
|
assert length(activity.recipients) == 3
|
|
|
|
%{mentions: [mention] = mentions} = StatusView.render("status.json", %{activity: activity})
|
|
|
|
assert length(mentions) == 1
|
|
assert mention.url == recipient.ap_id
|
|
end
|
|
|
|
test "attachments" do
|
|
object = %{
|
|
"type" => "Image",
|
|
"url" => [
|
|
%{
|
|
"mediaType" => "image/png",
|
|
"href" => "someurl"
|
|
}
|
|
],
|
|
"uuid" => 6
|
|
}
|
|
|
|
expected = %{
|
|
id: "1638338801",
|
|
type: "image",
|
|
url: "someurl",
|
|
remote_url: "someurl",
|
|
preview_url: "someurl",
|
|
text_url: "someurl",
|
|
description: nil,
|
|
pleroma: %{mime_type: "image/png"}
|
|
}
|
|
|
|
assert expected == StatusView.render("attachment.json", %{attachment: object})
|
|
|
|
# If theres a "id", use that instead of the generated one
|
|
object = Map.put(object, "id", 2)
|
|
assert %{id: "2"} = StatusView.render("attachment.json", %{attachment: object})
|
|
end
|
|
|
|
test "a reblog" do
|
|
user = insert(:user)
|
|
activity = insert(:note_activity)
|
|
|
|
{:ok, reblog, _} = CommonAPI.repeat(activity.id, user)
|
|
|
|
represented = StatusView.render("status.json", %{for: user, activity: reblog})
|
|
|
|
assert represented[:id] == to_string(reblog.id)
|
|
assert represented[:reblog][:id] == to_string(activity.id)
|
|
assert represented[:emojis] == []
|
|
end
|
|
|
|
test "a peertube video" do
|
|
user = insert(:user)
|
|
|
|
{:ok, object} =
|
|
Pleroma.Object.Fetcher.fetch_object_from_id(
|
|
"https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
|
|
)
|
|
|
|
%Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
|
|
|
|
represented = StatusView.render("status.json", %{for: user, activity: activity})
|
|
|
|
assert represented[:id] == to_string(activity.id)
|
|
assert length(represented[:media_attachments]) == 1
|
|
end
|
|
|
|
describe "build_tags/1" do
|
|
test "it returns a a dictionary tags" do
|
|
object_tags = [
|
|
"fediverse",
|
|
"mastodon",
|
|
"nextcloud",
|
|
%{
|
|
"href" => "https://kawen.space/users/lain",
|
|
"name" => "@lain@kawen.space",
|
|
"type" => "Mention"
|
|
}
|
|
]
|
|
|
|
assert StatusView.build_tags(object_tags) == [
|
|
%{name: "fediverse", url: "/tag/fediverse"},
|
|
%{name: "mastodon", url: "/tag/mastodon"},
|
|
%{name: "nextcloud", url: "/tag/nextcloud"}
|
|
]
|
|
end
|
|
end
|
|
|
|
describe "rich media cards" do
|
|
test "a rich media card without a site name renders correctly" do
|
|
page_url = "http://example.com"
|
|
|
|
card = %{
|
|
url: page_url,
|
|
image: page_url <> "/example.jpg",
|
|
title: "Example website"
|
|
}
|
|
|
|
%{provider_name: "example.com"} =
|
|
StatusView.render("card.json", %{page_url: page_url, rich_media: card})
|
|
end
|
|
|
|
test "a rich media card without a site name or image renders correctly" do
|
|
page_url = "http://example.com"
|
|
|
|
card = %{
|
|
url: page_url,
|
|
title: "Example website"
|
|
}
|
|
|
|
%{provider_name: "example.com"} =
|
|
StatusView.render("card.json", %{page_url: page_url, rich_media: card})
|
|
end
|
|
|
|
test "a rich media card without an image renders correctly" do
|
|
page_url = "http://example.com"
|
|
|
|
card = %{
|
|
url: page_url,
|
|
site_name: "Example site name",
|
|
title: "Example website"
|
|
}
|
|
|
|
%{provider_name: "Example site name"} =
|
|
StatusView.render("card.json", %{page_url: page_url, rich_media: card})
|
|
end
|
|
|
|
test "a rich media card with all relevant data renders correctly" do
|
|
page_url = "http://example.com"
|
|
|
|
card = %{
|
|
url: page_url,
|
|
site_name: "Example site name",
|
|
title: "Example website",
|
|
image: page_url <> "/example.jpg",
|
|
description: "Example description"
|
|
}
|
|
|
|
%{provider_name: "Example site name"} =
|
|
StatusView.render("card.json", %{page_url: page_url, rich_media: card})
|
|
end
|
|
end
|
|
|
|
describe "poll view" do
|
|
test "renders a poll" do
|
|
user = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(user, %{
|
|
"status" => "Is Tenshi eating a corndog cute?",
|
|
"poll" => %{
|
|
"options" => ["absolutely!", "sure", "yes", "why are you even asking?"],
|
|
"expires_in" => 20
|
|
}
|
|
})
|
|
|
|
object = Object.normalize(activity)
|
|
|
|
expected = %{
|
|
emojis: [],
|
|
expired: false,
|
|
id: to_string(object.id),
|
|
multiple: false,
|
|
options: [
|
|
%{title: "absolutely!", votes_count: 0},
|
|
%{title: "sure", votes_count: 0},
|
|
%{title: "yes", votes_count: 0},
|
|
%{title: "why are you even asking?", votes_count: 0}
|
|
],
|
|
voted: false,
|
|
votes_count: 0
|
|
}
|
|
|
|
result = StatusView.render("poll.json", %{object: object})
|
|
expires_at = result.expires_at
|
|
result = Map.delete(result, :expires_at)
|
|
|
|
assert result == expected
|
|
|
|
expires_at = NaiveDateTime.from_iso8601!(expires_at)
|
|
assert NaiveDateTime.diff(expires_at, NaiveDateTime.utc_now()) in 15..20
|
|
end
|
|
|
|
test "detects if it is multiple choice" do
|
|
user = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(user, %{
|
|
"status" => "Which Mastodon developer is your favourite?",
|
|
"poll" => %{
|
|
"options" => ["Gargron", "Eugen"],
|
|
"expires_in" => 20,
|
|
"multiple" => true
|
|
}
|
|
})
|
|
|
|
object = Object.normalize(activity)
|
|
|
|
assert %{multiple: true} = StatusView.render("poll.json", %{object: object})
|
|
end
|
|
|
|
test "detects emoji" do
|
|
user = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(user, %{
|
|
"status" => "What's with the smug face?",
|
|
"poll" => %{
|
|
"options" => [":blank: sip", ":blank::blank: sip", ":blank::blank::blank: sip"],
|
|
"expires_in" => 20
|
|
}
|
|
})
|
|
|
|
object = Object.normalize(activity)
|
|
|
|
assert %{emojis: [%{shortcode: "blank"}]} =
|
|
StatusView.render("poll.json", %{object: object})
|
|
end
|
|
|
|
test "detects vote status" do
|
|
user = insert(:user)
|
|
other_user = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(user, %{
|
|
"status" => "Which input devices do you use?",
|
|
"poll" => %{
|
|
"options" => ["mouse", "trackball", "trackpoint"],
|
|
"multiple" => true,
|
|
"expires_in" => 20
|
|
}
|
|
})
|
|
|
|
object = Object.normalize(activity)
|
|
|
|
{:ok, _, object} = CommonAPI.vote(other_user, object, [1, 2])
|
|
|
|
result = StatusView.render("poll.json", %{object: object, for: other_user})
|
|
|
|
assert result[:voted] == true
|
|
assert Enum.at(result[:options], 1)[:votes_count] == 1
|
|
assert Enum.at(result[:options], 2)[:votes_count] == 1
|
|
end
|
|
end
|
|
|
|
test "embeds a relationship in the account" do
|
|
user = insert(:user)
|
|
other_user = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(user, %{
|
|
"status" => "drink more water"
|
|
})
|
|
|
|
result = StatusView.render("status.json", %{activity: activity, for: other_user})
|
|
|
|
assert result[:account][:pleroma][:relationship] ==
|
|
AccountView.render("relationship.json", %{user: other_user, target: user})
|
|
end
|
|
|
|
test "embeds a relationship in the account in reposts" do
|
|
user = insert(:user)
|
|
other_user = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(user, %{
|
|
"status" => "˙˙ɐʎns"
|
|
})
|
|
|
|
{:ok, activity, _object} = CommonAPI.repeat(activity.id, other_user)
|
|
|
|
result = StatusView.render("status.json", %{activity: activity, for: user})
|
|
|
|
assert result[:account][:pleroma][:relationship] ==
|
|
AccountView.render("relationship.json", %{user: user, target: other_user})
|
|
|
|
assert result[:reblog][:account][:pleroma][:relationship] ==
|
|
AccountView.render("relationship.json", %{user: user, target: user})
|
|
end
|
|
|
|
test "visibility/list" do
|
|
user = insert(:user)
|
|
|
|
{:ok, list} = Pleroma.List.create("foo", user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
|
|
|
|
status = StatusView.render("status.json", activity: activity)
|
|
|
|
assert status.visibility == "list"
|
|
end
|
|
end
|