diff options
36 files changed, 765 insertions, 128 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index ef550be3e..8d251050c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - rel="me" was missing its cache ### Removed +- BREAKING: Support for passwords generated with `crypt(3)` (Gnu Social migration artifact) ## 2.5.1 diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md index 8c02201e6..f2812346b 100644 --- a/docs/installation/otp_en.md +++ b/docs/installation/otp_en.md @@ -2,15 +2,16 @@ {! backend/installation/otp_vs_from_source.include !} -This guide covers a installation using an OTP release. To install Pleroma from source, please check out the corresponding guide for your distro. +This guide covers a installation using OTP releases as built by the Pleroma project, it is meant as a fallback to distribution packages/recipes which are the preferred installation method. +To install Pleroma from source, please check out the corresponding guide for your distro. ## Pre-requisites -* A machine running Linux with GNU (e.g. Debian, Ubuntu) or musl (e.g. Alpine) libc and `x86_64`, `aarch64` or `armv7l` CPU, you have root access to. If you are not sure if it's compatible see [Detecting flavour section](#detecting-flavour) below +* A machine you have root access to running Debian GNU/Linux or compatible (eg. Ubuntu), or Alpine on `x86_64`, `aarch64` or `armv7l` CPU. If you are not sure what you are running see [Detecting flavour section](#detecting-flavour) below * A (sub)domain pointed to the machine -You will be running commands as root. If you aren't root already, please elevate your privileges by executing `sudo su`/`su`. +You will be running commands as root. If you aren't root already, please elevate your privileges by executing `sudo -i`/`su`. -While in theory OTP releases are possbile to install on any compatible machine, for the sake of simplicity this guide focuses only on Debian/Ubuntu and Alpine. +Similarly to other binaries, OTP releases tend to be only compatible with the distro they are built on, as such this guide focuses only on Debian/Ubuntu and Alpine. ### Detecting flavour @@ -19,7 +20,7 @@ Paste the following into the shell: arch="$(uname -m)";if [ "$arch" = "x86_64" ];then arch="amd64";elif [ "$arch" = "armv7l" ];then arch="arm";elif [ "$arch" = "aarch64" ];then arch="arm64";else echo "Unsupported arch: $arch">&2;fi;if getconf GNU_LIBC_VERSION>/dev/null;then libc_postfix="";elif [ "$(ldd 2>&1|head -c 9)" = "musl libc" ];then libc_postfix="-musl";elif [ "$(find /lib/libc.musl*|wc -l)" ];then libc_postfix="-musl";else echo "Unsupported libc">&2;fi;echo "$arch$libc_postfix" ``` -If your platform is supported the output will contain the flavour string, you will need it later. If not, this just means that we don't build releases for your platform, you can still try installing from source. +This should give your flavour string. If not this just means that we don't build releases for your platform, you can still try installing from source. ### Installing the required packages diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index dd65d56ae..43a3447c3 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -51,6 +51,8 @@ defmodule Pleroma.Emoji do @doc "Returns the path of the emoji `name`." @spec get(String.t()) :: String.t() | nil def get(name) do + name = maybe_strip_name(name) + case :ets.lookup(@ets, name) do [{_, path}] -> path _ -> nil @@ -139,6 +141,57 @@ defmodule Pleroma.Emoji do def is_unicode_emoji?(_), do: false + @emoji_regex ~r/:[A-Za-z0-9_-]+(@.+)?:/ + + def is_custom_emoji?(s) when is_binary(s), do: Regex.match?(@emoji_regex, s) + + def is_custom_emoji?(_), do: false + + def maybe_strip_name(name) when is_binary(name), do: String.trim(name, ":") + + def maybe_strip_name(name), do: name + + def maybe_quote(name) when is_binary(name) do + if is_unicode_emoji?(name) do + name + else + if String.starts_with?(name, ":") do + name + else + ":#{name}:" + end + end + end + + def maybe_quote(name), do: name + + def emoji_url(%{"type" => "EmojiReact", "content" => _, "tag" => []}), do: nil + + def emoji_url(%{"type" => "EmojiReact", "content" => emoji, "tag" => tags}) do + emoji = maybe_strip_name(emoji) + + tag = + tags + |> Enum.find(fn tag -> + tag["type"] == "Emoji" && !is_nil(tag["name"]) && tag["name"] == emoji + end) + + if is_nil(tag) do + nil + else + tag + |> Map.get("icon") + |> Map.get("url") + end + end + + def emoji_url(_), do: nil + + def emoji_name_with_instance(name, url) do + url = url |> URI.parse() |> Map.get(:host) + "#{name}@#{url}" + end + emoji_qualification_map = emojis |> Enum.filter(&String.contains?(&1, "\uFE0F")) diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 532047599..8eab3a241 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -16,6 +16,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.CommonAPI.ActivityDraft + alias Pleroma.Web.Endpoint require Pleroma.Constants @@ -54,13 +55,87 @@ defmodule Pleroma.Web.ActivityPub.Builder do {:ok, data, []} end + defp unicode_emoji_react(_object, data, emoji) do + data + |> Map.put("content", emoji) + |> Map.put("type", "EmojiReact") + end + + defp add_emoji_content(data, emoji, url) do + tag = [ + %{ + "id" => url, + "type" => "Emoji", + "name" => Emoji.maybe_quote(emoji), + "icon" => %{ + "type" => "Image", + "url" => url + } + } + ] + + data + |> Map.put("content", Emoji.maybe_quote(emoji)) + |> Map.put("type", "EmojiReact") + |> Map.put("tag", tag) + end + + defp remote_custom_emoji_react( + %{data: %{"reactions" => existing_reactions}}, + data, + emoji + ) do + [emoji_code, instance] = String.split(Emoji.maybe_strip_name(emoji), "@") + + matching_reaction = + Enum.find( + existing_reactions, + fn [name, _, url] -> + if url != nil do + url = URI.parse(url) + url.host == instance && name == emoji_code + end + end + ) + + if matching_reaction do + [name, _, url] = matching_reaction + add_emoji_content(data, name, url) + else + {:error, "Could not react"} + end + end + + defp remote_custom_emoji_react(_object, _data, _emoji) do + {:error, "Could not react"} + end + + defp local_custom_emoji_react(data, emoji) do + with %{file: path} = emojo <- Emoji.get(emoji) do + url = "#{Endpoint.url()}#{path}" + add_emoji_content(data, emojo.code, url) + else + _ -> {:error, "Emoji does not exist"} + end + end + + defp custom_emoji_react(object, data, emoji) do + if String.contains?(emoji, "@") do + remote_custom_emoji_react(object, data, emoji) + else + local_custom_emoji_react(data, emoji) + end + end + @spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()} def emoji_react(actor, object, emoji) do with {:ok, data, meta} <- object_action(actor, object) do data = - data - |> Map.put("content", emoji) - |> Map.put("type", "EmojiReact") + if Emoji.is_unicode_emoji?(emoji) do + unicode_emoji_react(object, data, emoji) + else + custom_emoji_react(object, data, emoji) + end {:ok, data, meta} end diff --git a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex index 0858281e5..a0b82b325 100644 --- a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex @@ -5,8 +5,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do use Ecto.Schema + alias Pleroma.Emoji alias Pleroma.Object alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes + alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator import Ecto.Changeset import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations @@ -19,6 +21,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields message_fields() activity_fields() + embeds_many(:tag, TagValidator) end end @@ -43,7 +46,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do def changeset(struct, data) do struct - |> cast(data, __schema__(:fields)) + |> cast(data, __schema__(:fields) -- [:tag]) + |> cast_embed(:tag) end defp fix(data) do @@ -53,12 +57,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do |> CommonFixes.fix_actor() |> CommonFixes.fix_activity_addressing() - with %Object{} = object <- Object.normalize(data["object"]) do - data - |> CommonFixes.fix_activity_context(object) - |> CommonFixes.fix_object_action_recipients(object) - else - _ -> data + data = Map.put_new(data, "tag", []) + + case Object.normalize(data["object"]) do + %Object{} = object -> + data + |> CommonFixes.fix_activity_context(object) + |> CommonFixes.fix_object_action_recipients(object) + + _ -> + data end end @@ -82,11 +90,31 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do defp validate_emoji(cng) do content = get_field(cng, :content) - if Pleroma.Emoji.is_unicode_emoji?(content) do + if Emoji.is_unicode_emoji?(content) || Emoji.is_custom_emoji?(content) do cng else cng - |> add_error(:content, "must be a single character emoji") + |> add_error(:content, "is not a valid emoji") + end + end + + defp maybe_validate_tag_presence(cng) do + content = get_field(cng, :content) + + if Emoji.is_unicode_emoji?(content) do + cng + else + tag = get_field(cng, :tag) + emoji_name = Emoji.maybe_strip_name(content) + + case tag do + [%{name: ^emoji_name, type: "Emoji", icon: %{url: _}}] -> + cng + + _ -> + cng + |> add_error(:tag, "does not contain an Emoji tag") + end end end @@ -97,5 +125,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do |> validate_actor_presence() |> validate_object_presence() |> validate_emoji() + |> maybe_validate_tag_presence() end end diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 12a042a3e..448cd1829 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -326,21 +326,29 @@ defmodule Pleroma.Web.ActivityPub.Utils do {:ok, Object.t()} | {:error, Ecto.Changeset.t()} def add_emoji_reaction_to_object( - %Activity{data: %{"content" => emoji, "actor" => actor}}, + %Activity{data: %{"content" => emoji, "actor" => actor}} = activity, object ) do reactions = get_cached_emoji_reactions(object) + emoji = Pleroma.Emoji.maybe_strip_name(emoji) + url = maybe_emoji_url(emoji, activity) new_reactions = - case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do + case Enum.find_index(reactions, fn [candidate, _, candidate_url] -> + if is_nil(candidate_url) do + emoji == candidate + else + url == candidate_url + end + end) do nil -> - reactions ++ [[emoji, [actor]]] + reactions ++ [[emoji, [actor], url]] index -> List.update_at( reactions, index, - fn [emoji, users] -> [emoji, Enum.uniq([actor | users])] end + fn [emoji, users, url] -> [emoji, Enum.uniq([actor | users]), url] end ) end @@ -349,18 +357,40 @@ defmodule Pleroma.Web.ActivityPub.Utils do update_element_in_object("reaction", new_reactions, object, count) end + defp maybe_emoji_url( + name, + %Activity{ + data: %{ + "tag" => [ + %{"type" => "Emoji", "name" => name, "icon" => %{"url" => url}} + ] + } + } + ), + do: url + + defp maybe_emoji_url(_, _), do: nil + def emoji_count(reactions_list) do - Enum.reduce(reactions_list, 0, fn [_, users], acc -> acc + length(users) end) + Enum.reduce(reactions_list, 0, fn [_, users, _], acc -> acc + length(users) end) end def remove_emoji_reaction_from_object( - %Activity{data: %{"content" => emoji, "actor" => actor}}, + %Activity{data: %{"content" => emoji, "actor" => actor}} = activity, object ) do + emoji = Pleroma.Emoji.maybe_strip_name(emoji) reactions = get_cached_emoji_reactions(object) + url = maybe_emoji_url(emoji, activity) new_reactions = - case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do + case Enum.find_index(reactions, fn [candidate, _, candidate_url] -> + if is_nil(candidate_url) do + emoji == candidate + else + url == candidate_url + end + end) do nil -> reactions @@ -368,9 +398,9 @@ defmodule Pleroma.Web.ActivityPub.Utils do List.update_at( reactions, index, - fn [emoji, users] -> [emoji, List.delete(users, actor)] end + fn [emoji, users, url] -> [emoji, List.delete(users, actor), url] end ) - |> Enum.reject(fn [_, users] -> Enum.empty?(users) end) + |> Enum.reject(fn [_, users, _] -> Enum.empty?(users) end) end count = emoji_count(new_reactions) @@ -490,17 +520,37 @@ defmodule Pleroma.Web.ActivityPub.Utils do def get_latest_reaction(internal_activity_id, %{ap_id: ap_id}, emoji) do %{data: %{"object" => object_ap_id}} = Activity.get_by_id(internal_activity_id) + emoji = Pleroma.Emoji.maybe_quote(emoji) "EmojiReact" |> Activity.Queries.by_type() |> where(actor: ^ap_id) - |> where([activity], fragment("?->>'content' = ?", activity.data, ^emoji)) + |> custom_emoji_discriminator(emoji) |> Activity.Queries.by_object_id(object_ap_id) |> order_by([activity], fragment("? desc nulls last", activity.id)) |> limit(1) |> Repo.one() end + defp custom_emoji_discriminator(query, emoji) do + if String.contains?(emoji, "@") do + stripped = Pleroma.Emoji.maybe_strip_name(emoji) + [name, domain] = String.split(stripped, "@") + domain_pattern = "%/" <> domain <> "/%" + emoji_pattern = Pleroma.Emoji.maybe_quote(name) + + query + |> where([activity], fragment("?->>'content' = ? + AND EXISTS ( + SELECT FROM jsonb_array_elements(?->'tag') elem + WHERE elem->>'id' ILIKE ? + )", activity.data, ^emoji_pattern, activity.data, ^domain_pattern)) + else + query + |> where([activity], fragment("?->>'content' = ?", activity.data, ^emoji)) + end + end + #### Announce-related helpers @doc """ diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 294590186..f2897a3a3 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -452,7 +452,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do operationId: "AccountController.blocks", description: "View your blocks. See also accounts/:id/{block,unblock}", security: [%{"oAuth" => ["read:blocks"]}], - parameters: pagination_params(), + parameters: [with_relationships_param() | pagination_params()], responses: %{ 200 => Operation.response("Accounts", "application/json", array_of_accounts()) } diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex index 449659f4b..034722eb2 100644 --- a/lib/pleroma/web/feed/feed_view.ex +++ b/lib/pleroma/web/feed/feed_view.ex @@ -6,7 +6,6 @@ defmodule Pleroma.Web.Feed.FeedView do use Phoenix.HTML use Pleroma.Web, :view - alias Pleroma.Formatter alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.Gettext @@ -72,7 +71,9 @@ defmodule Pleroma.Web.Feed.FeedView do def last_activity(activities), do: List.last(activities) - def activity_title(%{"content" => content, "summary" => summary} = data, opts \\ %{}) do + def activity_title(%{"content" => content} = data, opts \\ %{}) do + summary = Map.get(data, "summary", "") + title = cond do summary != "" -> summary @@ -81,9 +82,8 @@ defmodule Pleroma.Web.Feed.FeedView do end title - |> Pleroma.Web.Metadata.Utils.scrub_html() - |> Pleroma.Emoji.Formatter.demojify() - |> Formatter.truncate(opts[:max_length], opts[:omission]) + |> Pleroma.Web.Metadata.Utils.scrub_html_and_truncate(opts[:max_length], opts[:omission]) + |> HtmlEntities.encode() end def activity_description(data) do diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 02dfc552f..c313a0e97 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -540,7 +540,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do conn |> add_link_headers(users) - |> render("index.json", users: users, for: user, as: :user) + |> render("index.json", + users: users, + for: user, + as: :user, + embed_relationships: embed_relationships?(params) + ) end @doc "GET /api/v1/accounts/lookup" diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index abf7f29ab..efd2a0af6 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -92,6 +92,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do "safe_dm_mentions" end, "pleroma_emoji_reactions", + "pleroma_custom_emoji_reactions", "pleroma_chat_messages", if Config.get([:instance, :show_reactions]) do "exposable_reactions" diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index b5b5b2376..2a51f3755 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -17,6 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.NotificationView alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.MediaProxy alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView defp object_id_for(%{data: %{"object" => %{"id" => id}}}) when is_binary(id), do: id @@ -145,7 +146,9 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do end defp put_emoji(response, activity) do - Map.put(response, :emoji, activity.data["content"]) + response + |> Map.put(:emoji, activity.data["content"]) + |> Map.put(:emoji_url, MediaProxy.url(Pleroma.Emoji.emoji_url(activity.data))) end defp put_chat_message(response, activity, reading_user, opts) do diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 0a8c98b44..95efd9fd0 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -340,8 +340,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do opts[:for], Map.get(opts, :with_muted, false) ) - |> Stream.map(fn {emoji, users} -> - build_emoji_map(emoji, users, opts[:for]) + |> Stream.map(fn {emoji, users, url} -> + build_emoji_map(emoji, users, url, opts[:for]) end) |> Enum.to_list() @@ -702,11 +702,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end end - defp build_emoji_map(emoji, users, current_user) do + defp build_emoji_map(emoji, users, url, current_user) do %{ - name: emoji, + name: Pleroma.Web.PleromaAPI.EmojiReactionView.emoji_name(emoji, url), count: length(users), - me: !!(current_user && current_user.ap_id in users) + url: MediaProxy.url(url), + me: !!(current_user && current_user.ap_id in users), + account_ids: Enum.map(users, fn user -> User.get_cached_by_ap_id(user).id end) } end diff --git a/lib/pleroma/web/metadata/providers/rel_me.ex b/lib/pleroma/web/metadata/providers/rel_me.ex index f0bee85c8..eabd8cb00 100644 --- a/lib/pleroma/web/metadata/providers/rel_me.ex +++ b/lib/pleroma/web/metadata/providers/rel_me.ex @@ -8,12 +8,20 @@ defmodule Pleroma.Web.Metadata.Providers.RelMe do @impl Provider def build_tags(%{user: user}) do - bio_tree = Floki.parse_fragment!(user.bio) + profile_tree = + user.bio + |> append_fields_tag(user.fields) + |> Floki.parse_fragment!() - (Floki.attribute(bio_tree, "link[rel~=me]", "href") ++ - Floki.attribute(bio_tree, "a[rel~=me]", "href")) + (Floki.attribute(profile_tree, "link[rel~=me]", "href") ++ + Floki.attribute(profile_tree, "a[rel~=me]", "href")) |> Enum.map(fn link -> {:link, [rel: "me", href: link], []} end) end + + defp append_fields_tag(bio, fields) do + fields + |> Enum.reduce(bio, fn %{"value" => v}, res -> res <> v end) + end end diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex index 15414a988..80a8be9a2 100644 --- a/lib/pleroma/web/metadata/utils.ex +++ b/lib/pleroma/web/metadata/utils.ex @@ -30,12 +30,13 @@ defmodule Pleroma.Web.Metadata.Utils do |> scrub_html_and_truncate_object_field(object) end - def scrub_html_and_truncate(content, max_length \\ 200) when is_binary(content) do + def scrub_html_and_truncate(content, max_length \\ 200, omission \\ "...") + when is_binary(content) do content |> scrub_html |> Emoji.Formatter.demojify() |> HtmlEntities.decode() - |> Formatter.truncate(max_length) + |> Formatter.truncate(max_length, omission) end def scrub_html(content) when is_binary(content) do diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex index 78fd0b219..e095fa04a 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex @@ -50,29 +50,35 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do if not with_muted, do: User.cached_muted_users_ap_ids(user), else: [] end - filter_emoji = fn emoji, users -> + filter_emoji = fn emoji, users, url -> case Enum.reject(users, &(&1 in exclude_ap_ids)) do [] -> nil - users -> {emoji, users} + users -> {emoji, users, url} end end reactions |> Stream.map(fn - [emoji, users] when is_list(users) -> filter_emoji.(emoji, users) - {emoji, users} when is_list(users) -> filter_emoji.(emoji, users) + [emoji, users, url] when is_list(users) -> filter_emoji.(emoji, users, url) + {emoji, users, url} when is_list(users) -> filter_emoji.(emoji, users, url) + {emoji, users} when is_list(users) -> filter_emoji.(emoji, users, nil) _ -> nil end) |> Stream.reject(&is_nil/1) end defp filter(reactions, %{emoji: emoji}) when is_binary(emoji) do - Enum.filter(reactions, fn [e, _] -> e == emoji end) + Enum.filter(reactions, fn [e, _, _] -> e == emoji end) end defp filter(reactions, _), do: reactions def create(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do + emoji = + emoji + |> Pleroma.Emoji.fully_qualify_emoji() + |> Pleroma.Emoji.maybe_quote() + with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji) do activity = Activity.get_by_id(activity_id) @@ -83,6 +89,11 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do end def delete(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do + emoji = + emoji + |> Pleroma.Emoji.fully_qualify_emoji() + |> Pleroma.Emoji.maybe_quote() + with {:ok, _activity} <- CommonAPI.unreact_with_emoji(activity_id, user, emoji) do activity = Activity.get_by_id(activity_id) diff --git a/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex b/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex index 68ebd8292..6df4ab9d0 100644 --- a/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex +++ b/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex @@ -7,17 +7,30 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionView do alias Pleroma.Web.MastodonAPI.AccountView + def emoji_name(emoji, nil), do: emoji + + def emoji_name(emoji, url) do + url = URI.parse(url) + + if url.host == Pleroma.Web.Endpoint.host() do + emoji + else + "#{emoji}@#{url.host}" + end + end + def render("index.json", %{emoji_reactions: emoji_reactions} = opts) do render_many(emoji_reactions, __MODULE__, "show.json", opts) end - def render("show.json", %{emoji_reaction: {emoji, user_ap_ids}, user: user}) do + def render("show.json", %{emoji_reaction: {emoji, user_ap_ids, url}, user: user}) do users = fetch_users(user_ap_ids) %{ - name: emoji, + name: emoji_name(emoji, url), count: length(users), accounts: render(AccountView, "index.json", users: users, for: user), + url: Pleroma.Web.MediaProxy.url(url), me: !!(user && user.ap_id in user_ap_ids) } end diff --git a/lib/pleroma/web/plugs/authentication_plug.ex b/lib/pleroma/web/plugs/authentication_plug.ex index a7fd697b5..f912a1542 100644 --- a/lib/pleroma/web/plugs/authentication_plug.ex +++ b/lib/pleroma/web/plugs/authentication_plug.ex @@ -38,10 +38,6 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do def call(conn, _), do: conn - def checkpw(password, "$6" <> _ = password_hash) do - :crypt.crypt(password, password_hash) == password_hash - end - def checkpw(password, "$2" <> _ = password_hash) do # Handle bcrypt passwords for Mastodon migration Bcrypt.verify_pass(password, password_hash) @@ -60,10 +56,6 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do do_update_password(user, password) end - def maybe_update_password(%User{password_hash: "$6" <> _} = user, password) do - do_update_password(user, password) - end - def maybe_update_password(user, _), do: {:ok, user} defp do_update_password(user, password) do diff --git a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex index 260338772..b774f7984 100644 --- a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex @@ -4,8 +4,8 @@ <id><%= @data["id"] %></id> <title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title> <content type="html"><%= activity_description(@data) %></content> - <published><%= to_rfc3339(@activity.data["published"]) %></published> - <updated><%= to_rfc3339(@activity.data["published"]) %></updated> + <published><%= to_rfc3339(@data["published"]) %></published> + <updated><%= to_rfc3339(@data["published"]) %></updated> <ostatus:conversation ref="<%= activity_context(@activity) %>"> <%= activity_context(@activity) %> </ostatus:conversation> diff --git a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex index 5c8f35fe4..7de98f736 100644 --- a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex +++ b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex @@ -4,7 +4,7 @@ <guid><%= @data["id"] %></guid> <title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title> <description><%= activity_description(@data) %></description> - <pubDate><%= to_rfc2822(@activity.data["published"]) %></pubDate> + <pubDate><%= to_rfc2822(@data["published"]) %></pubDate> <ostatus:conversation ref="<%= activity_context(@activity) %>"> <%= activity_context(@activity) %> </ostatus:conversation> diff --git a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex index 25980c1e4..03c222975 100644 --- a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex @@ -7,8 +7,8 @@ <id><%= @data["id"] %></id> <title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title> <content type="html"><%= activity_description(@data) %></content> - <published><%= to_rfc3339(@activity.data["published"]) %></published> - <updated><%= to_rfc3339(@activity.data["published"]) %></updated> + <published><%= to_rfc3339(@data["published"]) %></published> + <updated><%= to_rfc3339(@data["published"]) %></updated> <ostatus:conversation ref="<%= activity_context(@activity) %>"> <%= activity_context(@activity) %> </ostatus:conversation> diff --git a/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex b/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex index d582c83e8..1b8c34b87 100644 --- a/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex +++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex @@ -4,7 +4,7 @@ <guid isPermalink="true"><%= activity_context(@activity) %></guid> <link><%= activity_context(@activity) %></link> - <pubDate><%= to_rfc2822(@activity.data["published"]) %></pubDate> + <pubDate><%= to_rfc2822(@data["published"]) %></pubDate> <description><%= activity_description(@data) %></description> <%= for attachment <- @data["attachment"] || [] do %> diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index 3805293bc..794417612 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -45,5 +45,5 @@ defmodule Pleroma.Workers.BackgroundWorker do end @impl Oban.Worker - def timeout(_job), do: :timer.seconds(5) + def timeout(_job), do: :timer.seconds(900) end @@ -150,7 +150,6 @@ defmodule Pleroma.Mixfile do {:sweet_xml, "~> 0.7.2"}, {:earmark, "~> 1.4.22"}, {:bbcode_pleroma, "~> 0.2.0"}, - {:crypt, "~> 1.0"}, {:cors_plug, "~> 2.0"}, {:web_push_encryption, "~> 0.3.1"}, {:swoosh, "~> 1.0"}, @@ -21,7 +21,6 @@ "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, - "crypt": {:hex, :crypt, "1.0.1", "a3567e1c651a2ec42c6650d9f3ab789e0f12a508c060653a9bbb5fafe60f043c", [:rebar3], [], "hexpm", "968dffe321c7a5d9f9b4577c4a4ff56a1c26d1a8a2270eb22c7636a0b43d3982"}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, "db_connection": {:hex, :db_connection, "2.4.2", "f92e79aff2375299a16bcb069a14ee8615c3414863a6fef93156aee8e86c2ff3", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4fe53ca91b99f55ea249693a0229356a08f4d1a7931d8ffa79289b145fe83668"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, diff --git a/test/fixtures/custom-emoji-reaction.json b/test/fixtures/custom-emoji-reaction.json new file mode 100644 index 000000000..003de0511 --- /dev/null +++ b/test/fixtures/custom-emoji-reaction.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "Hashtag": "as:Hashtag" + } + ], + "type": "Like", + "id": "https://misskey.local.live/likes/917ocsybgp", + "actor": "https://misskey.local.live/users/8x8yep20u2", + "object": "https://pleroma.local.live/objects/89937a53-2692-4631-bb62-770091267391", + "content": ":hanapog:", + "_misskey_reaction": ":hanapog:", + "tag": [ + { + "id": "https://misskey.local.live/emojis/hanapog", + "type": "Emoji", + "name": ":hanapog:", + "updated": "2022-06-07T12:00:05.773Z", + "icon": { + "type": "Image", + "mediaType": "image/png", + "url": "https://misskey.local.live/files/webpublic-8f8a9768-7264-4171-88d6-2356aabeadcd" + } + } + ] +} diff --git a/test/pleroma/web/activity_pub/object_validators/emoji_react_handling_test.exs b/test/pleroma/web/activity_pub/object_validators/emoji_react_handling_test.exs index bbdb09c4c..9bb291a38 100644 --- a/test/pleroma/web/activity_pub/object_validators/emoji_react_handling_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/emoji_react_handling_test.exs @@ -38,16 +38,70 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactHandlingTest do assert {:content, {"can't be blank", [validation: :required]}} in cng.errors end - test "it is not valid with a non-emoji content field", %{valid_emoji_react: valid_emoji_react} do + test "it is valid when custom emoji is used", %{valid_emoji_react: valid_emoji_react} do without_emoji_content = valid_emoji_react - |> Map.put("content", "x") + |> Map.put("content", ":hello:") + |> Map.put("tag", [ + %{ + "type" => "Emoji", + "name" => ":hello:", + "icon" => %{"url" => "http://somewhere", "type" => "Image"} + } + ]) + + {:ok, _, _} = ObjectValidator.validate(without_emoji_content, []) + end + + test "it is not valid when custom emoji don't have a matching tag", %{ + valid_emoji_react: valid_emoji_react + } do + without_emoji_content = + valid_emoji_react + |> Map.put("content", ":hello:") + |> Map.put("tag", [ + %{ + "type" => "Emoji", + "name" => ":whoops:", + "icon" => %{"url" => "http://somewhere", "type" => "Image"} + } + ]) + + {:error, cng} = ObjectValidator.validate(without_emoji_content, []) + + refute cng.valid? + + assert {:tag, {"does not contain an Emoji tag", []}} in cng.errors + end + + test "it is not valid when custom emoji have no tags", %{ + valid_emoji_react: valid_emoji_react + } do + without_emoji_content = + valid_emoji_react + |> Map.put("content", ":hello:") + |> Map.put("tag", []) + + {:error, cng} = ObjectValidator.validate(without_emoji_content, []) + + refute cng.valid? + + assert {:tag, {"does not contain an Emoji tag", []}} in cng.errors + end + + test "it is not valid when custom emoji doesn't match a shortcode format", %{ + valid_emoji_react: valid_emoji_react + } do + without_emoji_content = + valid_emoji_react + |> Map.put("content", "hello") + |> Map.put("tag", []) {:error, cng} = ObjectValidator.validate(without_emoji_content, []) refute cng.valid? - assert {:content, {"must be a single character emoji", []}} in cng.errors + assert {:tag, {"does not contain an Emoji tag", []}} in cng.errors end end end diff --git a/test/pleroma/web/activity_pub/side_effects_test.exs b/test/pleroma/web/activity_pub/side_effects_test.exs index b24831e85..6820e23d0 100644 --- a/test/pleroma/web/activity_pub/side_effects_test.exs +++ b/test/pleroma/web/activity_pub/side_effects_test.exs @@ -453,7 +453,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do object = Object.get_by_ap_id(emoji_react.data["object"]) assert object.data["reaction_count"] == 1 - assert ["👌", [user.ap_id]] in object.data["reactions"] + assert ["👌", [user.ap_id], nil] in object.data["reactions"] end test "creates a notification", %{emoji_react: emoji_react, poster: poster} do diff --git a/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs index 83bf59c6f..f2e1cefa3 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs @@ -34,7 +34,56 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.EmojiReactHandlingTest do object = Object.get_by_ap_id(data["object"]) assert object.data["reaction_count"] == 1 - assert match?([["👌", _]], object.data["reactions"]) + assert match?([["👌", _, nil]], object.data["reactions"]) + end + + test "it works for incoming custom emoji reactions" do + user = insert(:user) + other_user = insert(:user, local: false) + {:ok, activity} = CommonAPI.post(user, %{status: "hello"}) + + data = + File.read!("test/fixtures/custom-emoji-reaction.json") + |> Jason.decode!() + |> Map.put("object", activity.data["object"]) + |> Map.put("actor", other_user.ap_id) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == other_user.ap_id + assert data["type"] == "EmojiReact" + assert data["id"] == "https://misskey.local.live/likes/917ocsybgp" + assert data["object"] == activity.data["object"] + assert data["content"] == ":hanapog:" + + assert data["tag"] == [ + %{ + "id" => "https://misskey.local.live/emojis/hanapog", + "type" => "Emoji", + "name" => "hanapog", + "updated" => "2022-06-07T12:00:05.773Z", + "icon" => %{ + "type" => "Image", + "url" => + "https://misskey.local.live/files/webpublic-8f8a9768-7264-4171-88d6-2356aabeadcd" + } + } + ] + + object = Object.get_by_ap_id(data["object"]) + + assert object.data["reaction_count"] == 1 + + assert match?( + [ + [ + "hanapog", + _, + "https://misskey.local.live/files/webpublic-8f8a9768-7264-4171-88d6-2356aabeadcd" + ] + ], + object.data["reactions"] + ) end test "it works for incoming unqualified emoji reactions" do @@ -65,7 +114,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.EmojiReactHandlingTest do object = Object.get_by_ap_id(data["object"]) assert object.data["reaction_count"] == 1 - assert match?([[^emoji, _]], object.data["reactions"]) + assert match?([[^emoji, _, _]], object.data["reactions"]) end test "it reject invalid emoji reactions" do diff --git a/test/pleroma/web/feed/user_controller_test.exs b/test/pleroma/web/feed/user_controller_test.exs index de32d3d4b..d3c4108de 100644 --- a/test/pleroma/web/feed/user_controller_test.exs +++ b/test/pleroma/web/feed/user_controller_test.exs @@ -57,9 +57,23 @@ defmodule Pleroma.Web.Feed.UserControllerTest do ) note_activity2 = insert(:note_activity, note: note2) + + note3 = + insert(:note, + user: user, + data: %{ + "content" => "This note tests whether HTML entities are truncated properly", + "summary" => "Won't, didn't fail", + "inReplyTo" => note_activity2.id + } + ) + + _note_activity3 = insert(:note_activity, note: note3) object = Object.normalize(note_activity, fetch: false) - [user: user, object: object, max_id: note_activity2.id] + encoded_title = FeedView.activity_title(note3.data) + + [user: user, object: object, max_id: note_activity2.id, encoded_title: encoded_title] end test "gets an atom feed", %{conn: conn, user: user, object: object, max_id: max_id} do @@ -74,7 +88,7 @@ defmodule Pleroma.Web.Feed.UserControllerTest do |> SweetXml.parse() |> SweetXml.xpath(~x"//entry/title/text()"l) - assert activity_titles == ['2hu', '2hu & as'] + assert activity_titles == ['Won\'t, didn\'...', '2hu', '2hu & as'] assert resp =~ FeedView.escape(object.data["content"]) assert resp =~ FeedView.escape(object.data["summary"]) assert resp =~ FeedView.escape(object.data["context"]) @@ -105,7 +119,7 @@ defmodule Pleroma.Web.Feed.UserControllerTest do |> SweetXml.parse() |> SweetXml.xpath(~x"//item/title/text()"l) - assert activity_titles == ['2hu', '2hu & as'] + assert activity_titles == ['Won\'t, didn\'...', '2hu', '2hu & as'] assert resp =~ FeedView.escape(object.data["content"]) assert resp =~ FeedView.escape(object.data["summary"]) assert resp =~ FeedView.escape(object.data["context"]) @@ -176,6 +190,30 @@ defmodule Pleroma.Web.Feed.UserControllerTest do |> get("/users/#{user.nickname}/feed.rss") |> response(200) end + + test "does not mangle HTML entities midway", %{ + conn: conn, + user: user, + object: object, + encoded_title: encoded_title + } do + resp = + conn + |> put_req_header("accept", "application/atom+xml") + |> get(user_feed_path(conn, :feed, user.nickname)) + |> response(200) + + activity_titles = + resp + |> SweetXml.parse() + |> SweetXml.xpath(~x"//entry/title/text()"l) + + assert activity_titles == ['Won\'t, didn\'...', '2hu', '2hu & as'] + assert resp =~ FeedView.escape(object.data["content"]) + assert resp =~ FeedView.escape(object.data["summary"]) + assert resp =~ FeedView.escape(object.data["context"]) + assert resp =~ encoded_title + end end # Note: see ActivityPubControllerTest for JSON format tests diff --git a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs index 958b7f76f..128e60b0a 100644 --- a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs @@ -2031,6 +2031,39 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do assert [%{"id" => ^id1}] = result end + test "list of blocks with with_relationships parameter" do + %{user: user, conn: conn} = oauth_access(["read:blocks"]) + %{id: id1} = other_user1 = insert(:user) + %{id: id2} = other_user2 = insert(:user) + %{id: id3} = other_user3 = insert(:user) + + {:ok, _, _} = User.follow(other_user1, user) + {:ok, _, _} = User.follow(other_user2, user) + {:ok, _, _} = User.follow(other_user3, user) + + {:ok, _} = User.block(user, other_user1) + {:ok, _} = User.block(user, other_user2) + {:ok, _} = User.block(user, other_user3) + + assert [ + %{ + "id" => ^id3, + "pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}} + }, + %{ + "id" => ^id2, + "pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}} + }, + %{ + "id" => ^id1, + "pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}} + } + ] = + conn + |> get("/api/v1/blocks?with_relationships=true") + |> json_response_and_validate_schema(200) + end + test "account lookup", %{conn: conn} do %{nickname: acct} = insert(:user, %{nickname: "nickname"}) %{nickname: acct_two} = insert(:user, %{nickname: "nickname@notlocaldoma.in"}) diff --git a/test/pleroma/web/mastodon_api/views/notification_view_test.exs b/test/pleroma/web/mastodon_api/views/notification_view_test.exs index 6ea894691..ddbe4557f 100644 --- a/test/pleroma/web/mastodon_api/views/notification_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/notification_view_test.exs @@ -190,7 +190,47 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do emoji: "☕", account: AccountView.render("show.json", %{user: other_user, for: user}), status: StatusView.render("show.json", %{activity: activity, for: user}), - created_at: Utils.to_masto_date(notification.inserted_at) + created_at: Utils.to_masto_date(notification.inserted_at), + emoji_url: nil + } + + test_notifications_rendering([notification], user, [expected]) + end + + test "EmojiReact custom emoji notification" do + user = insert(:user) + other_user = insert(:user) + + note = + insert(:note, + user: user, + data: %{ + "reactions" => [ + ["👍", [user.ap_id], nil], + ["dinosaur", [user.ap_id], "http://localhost:4001/emoji/dino walking.gif"] + ] + } + ) + + activity = insert(:note_activity, note: note, user: user) + + {:ok, _activity} = CommonAPI.react_with_emoji(activity.id, other_user, "dinosaur") + + activity = Repo.get(Activity, activity.id) + + [notification] = Notification.for_user(user) + + assert notification + + expected = %{ + id: to_string(notification.id), + pleroma: %{is_seen: false, is_muted: false}, + type: "pleroma:emoji_reaction", + emoji: ":dinosaur:", + account: AccountView.render("show.json", %{user: other_user, for: user}), + status: StatusView.render("show.json", %{activity: activity, for: user}), + created_at: Utils.to_masto_date(notification.inserted_at), + emoji_url: "http://localhost:4001/emoji/dino walking.gif" } test_notifications_rendering([notification], user, [expected]) diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index f76b115b7..a1f3e3e1d 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -35,16 +35,26 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do {:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"}) {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "☕") + {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, ":dinosaur:") {:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "🍵") {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") + {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, ":dinosaur:") + activity = Repo.get(Activity, activity.id) status = StatusView.render("show.json", activity: activity) assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec()) assert status[:pleroma][:emoji_reactions] == [ - %{name: "☕", count: 2, me: false}, - %{name: "🍵", count: 1, me: false} + %{name: "☕", count: 2, me: false, url: nil, account_ids: [other_user.id, user.id]}, + %{ + count: 2, + me: false, + name: "dinosaur", + url: "http://localhost:4001/emoji/dino walking.gif", + account_ids: [other_user.id, user.id] + }, + %{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]} ] status = StatusView.render("show.json", activity: activity, for: user) @@ -52,8 +62,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec()) assert status[:pleroma][:emoji_reactions] == [ - %{name: "☕", count: 2, me: true}, - %{name: "🍵", count: 1, me: false} + %{name: "☕", count: 2, me: true, url: nil, account_ids: [other_user.id, user.id]}, + %{ + count: 2, + me: true, + name: "dinosaur", + url: "http://localhost:4001/emoji/dino walking.gif", + account_ids: [other_user.id, user.id] + }, + %{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]} ] end @@ -66,11 +83,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do |> Object.update_data(%{"reactions" => %{"☕" => [user.ap_id], "x" => 1}}) activity = Activity.get_by_id(activity.id) - status = StatusView.render("show.json", activity: activity, for: user) assert status[:pleroma][:emoji_reactions] == [ - %{name: "☕", count: 1, me: true} + %{name: "☕", count: 1, me: true, url: nil, account_ids: [user.id]} ] end @@ -90,7 +106,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do status = StatusView.render("show.json", activity: activity) assert status[:pleroma][:emoji_reactions] == [ - %{name: "☕", count: 1, me: false} + %{name: "☕", count: 1, me: false, url: nil, account_ids: [other_user.id]} ] status = StatusView.render("show.json", activity: activity, for: user) @@ -102,19 +118,25 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do status = StatusView.render("show.json", activity: activity) assert status[:pleroma][:emoji_reactions] == [ - %{name: "☕", count: 2, me: false} + %{ + name: "☕", + count: 2, + me: false, + url: nil, + account_ids: [third_user.id, other_user.id] + } ] status = StatusView.render("show.json", activity: activity, for: user) assert status[:pleroma][:emoji_reactions] == [ - %{name: "☕", count: 1, me: false} + %{name: "☕", count: 1, me: false, url: nil, account_ids: [third_user.id]} ] status = StatusView.render("show.json", activity: activity, for: other_user) assert status[:pleroma][:emoji_reactions] == [ - %{name: "☕", count: 1, me: true} + %{name: "☕", count: 1, me: true, url: nil, account_ids: [other_user.id]} ] end diff --git a/test/pleroma/web/metadata/providers/rel_me_test.exs b/test/pleroma/web/metadata/providers/rel_me_test.exs index cce4f3607..793669037 100644 --- a/test/pleroma/web/metadata/providers/rel_me_test.exs +++ b/test/pleroma/web/metadata/providers/rel_me_test.exs @@ -11,11 +11,24 @@ defmodule Pleroma.Web.Metadata.Providers.RelMeTest do bio = ~s(<a href="https://some-link.com">https://some-link.com</a> <a rel="me" href="https://another-link.com">https://another-link.com</a> <link href="http://some.com"> <link rel="me" href="http://some3.com">) - user = insert(:user, %{bio: bio}) + fields = [ + %{ + "name" => "profile", + "value" => ~S(<a rel="me" href="http://profile.com">http://profile.com</a>) + }, + %{ + "name" => "like", + "value" => ~S(<a href="http://cofe.io">http://cofe.io</a>) + }, + %{"name" => "foo", "value" => "bar"} + ] + + user = insert(:user, %{bio: bio, fields: fields}) assert RelMe.build_tags(%{user: user}) == [ {:link, [rel: "me", href: "http://some3.com"], []}, - {:link, [rel: "me", href: "https://another-link.com"], []} + {:link, [rel: "me", href: "https://another-link.com"], []}, + {:link, [rel: "me", href: "http://profile.com"], []} ] end end diff --git a/test/pleroma/web/metadata/utils_test.exs b/test/pleroma/web/metadata/utils_test.exs index 85ef6033a..3daf852fb 100644 --- a/test/pleroma/web/metadata/utils_test.exs +++ b/test/pleroma/web/metadata/utils_test.exs @@ -72,7 +72,7 @@ defmodule Pleroma.Web.Metadata.UtilsTest do end end - describe "scrub_html_and_truncate/2" do + describe "scrub_html_and_truncate/3" do test "it returns text without encode HTML" do assert Utils.scrub_html_and_truncate("Pleroma's really cool!") == "Pleroma's really cool!" end diff --git a/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs index 77c75b560..d81b47a6b 100644 --- a/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs @@ -17,23 +17,113 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do user = insert(:user) other_user = insert(:user) + note = insert(:note, user: user, data: %{"reactions" => [["👍", [other_user.ap_id], nil]]}) + activity = insert(:note_activity, note: note, user: user) + + result = + conn + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) + |> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/\u26A0") + |> json_response_and_validate_schema(200) + + assert %{"id" => id} = result + assert to_string(activity.id) == id + + assert result["pleroma"]["emoji_reactions"] == [ + %{ + "name" => "👍", + "count" => 1, + "me" => true, + "url" => nil, + "account_ids" => [other_user.id] + }, + %{ + "name" => "\u26A0\uFE0F", + "count" => 1, + "me" => true, + "url" => nil, + "account_ids" => [other_user.id] + } + ] + {:ok, activity} = CommonAPI.post(user, %{status: "#cofe"}) + ObanHelpers.perform_all() + + # Reacting with a custom emoji result = conn |> assign(:user, other_user) |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) - |> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/☕") + |> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/:dinosaur:") |> json_response_and_validate_schema(200) - # We return the status, but this our implementation detail. assert %{"id" => id} = result assert to_string(activity.id) == id assert result["pleroma"]["emoji_reactions"] == [ - %{"name" => "☕", "count" => 1, "me" => true} + %{ + "name" => "dinosaur", + "count" => 1, + "me" => true, + "url" => "http://localhost:4001/emoji/dino walking.gif", + "account_ids" => [other_user.id] + } + ] + + # Reacting with a remote emoji + note = + insert(:note, + user: user, + data: %{ + "reactions" => [ + ["👍", [other_user.ap_id], nil], + ["wow", [other_user.ap_id], "https://remote/emoji/wow"] + ] + } + ) + + activity = insert(:note_activity, note: note, user: user) + + result = + conn + |> assign(:user, user) + |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:statuses"])) + |> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/:wow@remote:") + |> json_response(200) + + assert result["pleroma"]["emoji_reactions"] == [ + %{ + "account_ids" => [other_user.id], + "count" => 1, + "me" => false, + "name" => "👍", + "url" => nil + }, + %{ + "name" => "wow@remote", + "count" => 2, + "me" => true, + "url" => "https://remote/emoji/wow", + "account_ids" => [user.id, other_user.id] + } ] + # Reacting with a remote custom emoji that hasn't been reacted with yet + note = + insert(:note, + user: user + ) + + activity = insert(:note_activity, note: note, user: user) + + assert conn + |> assign(:user, user) + |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:statuses"])) + |> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/:wow@remote:") + |> json_response(400) + # Reacting with a non-emoji assert conn |> assign(:user, other_user) @@ -46,8 +136,21 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do user = insert(:user) other_user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{status: "#cofe"}) + note = + insert(:note, + user: user, + data: %{"reactions" => [["wow", [user.ap_id], "https://remote/emoji/wow"]]} + ) + + activity = insert(:note_activity, note: note, user: user) + + ObanHelpers.perform_all() + {:ok, _reaction_activity} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") + {:ok, _reaction_activity} = CommonAPI.react_with_emoji(activity.id, other_user, ":dinosaur:") + + {:ok, _reaction_activity} = + CommonAPI.react_with_emoji(activity.id, other_user, ":wow@remote:") ObanHelpers.perform_all() @@ -60,11 +163,47 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do assert %{"id" => id} = json_response_and_validate_schema(result, 200) assert to_string(activity.id) == id + # Remove custom emoji + + result = + conn + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) + |> delete("/api/v1/pleroma/statuses/#{activity.id}/reactions/:dinosaur:") + + assert %{"id" => id} = json_response_and_validate_schema(result, 200) + assert to_string(activity.id) == id + ObanHelpers.perform_all() object = Object.get_by_ap_id(activity.data["object"]) - assert object.data["reaction_count"] == 0 + assert object.data["reaction_count"] == 2 + + # Remove custom remote emoji + result = + conn + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) + |> delete("/api/v1/pleroma/statuses/#{activity.id}/reactions/:wow@remote:") + |> json_response(200) + + assert result["pleroma"]["emoji_reactions"] == [ + %{ + "name" => "wow@remote", + "count" => 1, + "me" => false, + "url" => "https://remote/emoji/wow", + "account_ids" => [user.id] + } + ] + + # Remove custom remote emoji that hasn't been reacted with yet + assert conn + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) + |> delete("/api/v1/pleroma/statuses/#{activity.id}/reactions/:zoop@remote:") + |> json_response(400) end test "GET /api/v1/pleroma/statuses/:id/reactions", %{conn: conn} do @@ -181,7 +320,15 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅") {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") - assert [%{"name" => "🎅", "count" => 1, "accounts" => [represented_user], "me" => false}] = + assert [ + %{ + "name" => "🎅", + "count" => 1, + "accounts" => [represented_user], + "me" => false, + "url" => nil + } + ] = conn |> get("/api/v1/pleroma/statuses/#{activity.id}/reactions/🎅") |> json_response_and_validate_schema(200) diff --git a/test/pleroma/web/plugs/authentication_plug_test.exs b/test/pleroma/web/plugs/authentication_plug_test.exs index 41fdb93bc..b8acd01c5 100644 --- a/test/pleroma/web/plugs/authentication_plug_test.exs +++ b/test/pleroma/web/plugs/authentication_plug_test.exs @@ -70,28 +70,6 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do assert "$pbkdf2" <> _ = user.password_hash end - @tag :skip_on_mac - test "with a crypt hash, it updates to a pkbdf2 hash", %{conn: conn} do - user = - insert(:user, - password_hash: - "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1" - ) - - conn = - conn - |> assign(:auth_user, user) - |> assign(:auth_credentials, %{password: "password"}) - |> AuthenticationPlug.call(%{}) - - assert conn.assigns.user.id == conn.assigns.auth_user.id - assert conn.assigns.token == nil - assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug) - - user = User.get_by_id(user.id) - assert "$pbkdf2" <> _ = user.password_hash - end - describe "checkpw/2" do test "check pbkdf2 hash" do hash = @@ -101,14 +79,6 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do refute AuthenticationPlug.checkpw("test-password1", hash) end - @tag :skip_on_mac - test "check sha512-crypt hash" do - hash = - "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1" - - assert AuthenticationPlug.checkpw("password", hash) - end - test "check bcrypt hash" do hash = "$2a$10$uyhC/R/zoE1ndwwCtMusK.TLVzkQ/Ugsbqp3uXI.CTTz0gBw.24jS" |