From 7c8abbcb1ccafa79372fd75fdec87db2207b61ec Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 26 Jul 2019 15:33:25 +0200 Subject: [PATCH 01/14] CHANGELOG.md: Add entry for !1484 Related to: https://git.pleroma.social/pleroma/pleroma/merge_requests/1484 [ci skip] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3f54d19e..bd3048b19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Changed - **Breaking:** Configuration: A setting to explicitly disable the mailer was added, defaulting to true, if you are using a mailer add `config :pleroma, Pleroma.Emails.Mailer, enabled: true` to your config +- **Breaking:** Configuration: `/media/` is now removed when `base_url` is configured, append `/media/` to your `base_url` config to keep the old behaviour if desired - Configuration: OpenGraph and TwitterCard providers enabled by default - Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text - Federation: Return 403 errors when trying to request pages from a user's follower/following collections if they have `hide_followers`/`hide_follows` set From 6b77a88365f3a58cf8d1f9c00ed04532f706b87c Mon Sep 17 00:00:00 2001 From: Maksim Date: Fri, 26 Jul 2019 20:27:38 +0000 Subject: [PATCH 02/14] [#1097] added redirect: /pleroma/admin -> /pleroma/admin/ --- .../web/fallback_redirect_controller.ex | 77 +++++++++++++++++++ lib/pleroma/web/router.ex | 65 ---------------- test/web/fallback_test.exs | 4 + 3 files changed, 81 insertions(+), 65 deletions(-) create mode 100644 lib/pleroma/web/fallback_redirect_controller.ex diff --git a/lib/pleroma/web/fallback_redirect_controller.ex b/lib/pleroma/web/fallback_redirect_controller.ex new file mode 100644 index 000000000..5fbf3695f --- /dev/null +++ b/lib/pleroma/web/fallback_redirect_controller.ex @@ -0,0 +1,77 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Fallback.RedirectController do + use Pleroma.Web, :controller + require Logger + alias Pleroma.User + alias Pleroma.Web.Metadata + + def api_not_implemented(conn, _params) do + conn + |> put_status(404) + |> json(%{error: "Not implemented"}) + end + + def redirector(conn, _params, code \\ 200) + + # redirect to admin section + # /pleroma/admin -> /pleroma/admin/ + # + def redirector(conn, %{"path" => ["pleroma", "admin"]} = _, _code) do + redirect(conn, to: "/pleroma/admin/") + end + + def redirector(conn, _params, code) do + conn + |> put_resp_content_type("text/html") + |> send_file(code, index_file_path()) + end + + def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id} = params) do + with %User{} = user <- User.get_cached_by_nickname_or_id(maybe_nickname_or_id) do + redirector_with_meta(conn, %{user: user}) + else + nil -> + redirector(conn, params) + end + end + + def redirector_with_meta(conn, params) do + {:ok, index_content} = File.read(index_file_path()) + + tags = + try do + Metadata.build_tags(params) + rescue + e -> + Logger.error( + "Metadata rendering for #{conn.request_path} failed.\n" <> + Exception.format(:error, e, __STACKTRACE__) + ) + + "" + end + + response = String.replace(index_content, "", tags) + + conn + |> put_resp_content_type("text/html") + |> send_resp(200, response) + end + + def index_file_path do + Pleroma.Plugs.InstanceStatic.file_path("index.html") + end + + def registration_page(conn, params) do + redirector(conn, params) + end + + def empty(conn, _params) do + conn + |> put_status(204) + |> text("") + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index a9f3826fc..47ee762dc 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -729,68 +729,3 @@ defmodule Pleroma.Web.Router do options("/*path", RedirectController, :empty) end end - -defmodule Fallback.RedirectController do - use Pleroma.Web, :controller - require Logger - alias Pleroma.User - alias Pleroma.Web.Metadata - - def api_not_implemented(conn, _params) do - conn - |> put_status(404) - |> json(%{error: "Not implemented"}) - end - - def redirector(conn, _params, code \\ 200) do - conn - |> put_resp_content_type("text/html") - |> send_file(code, index_file_path()) - end - - def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id} = params) do - with %User{} = user <- User.get_cached_by_nickname_or_id(maybe_nickname_or_id) do - redirector_with_meta(conn, %{user: user}) - else - nil -> - redirector(conn, params) - end - end - - def redirector_with_meta(conn, params) do - {:ok, index_content} = File.read(index_file_path()) - - tags = - try do - Metadata.build_tags(params) - rescue - e -> - Logger.error( - "Metadata rendering for #{conn.request_path} failed.\n" <> - Exception.format(:error, e, __STACKTRACE__) - ) - - "" - end - - response = String.replace(index_content, "", tags) - - conn - |> put_resp_content_type("text/html") - |> send_resp(200, response) - end - - def index_file_path do - Pleroma.Plugs.InstanceStatic.file_path("index.html") - end - - def registration_page(conn, params) do - redirector(conn, params) - end - - def empty(conn, _params) do - conn - |> put_status(204) - |> text("") - end -end diff --git a/test/web/fallback_test.exs b/test/web/fallback_test.exs index cc78b3ae1..c13db9526 100644 --- a/test/web/fallback_test.exs +++ b/test/web/fallback_test.exs @@ -30,6 +30,10 @@ defmodule Pleroma.Web.FallbackTest do |> json_response(404) == %{"error" => "Not implemented"} end + test "GET /pleroma/admin -> /pleroma/admin/", %{conn: conn} do + assert redirected_to(get(conn, "/pleroma/admin")) =~ "/pleroma/admin/" + end + test "GET /*path", %{conn: conn} do assert conn |> get("/foo") From 961e7785314688b9e2445649c71e12023a982165 Mon Sep 17 00:00:00 2001 From: Thomas Sileo Date: Sun, 28 Jul 2019 14:17:56 +0200 Subject: [PATCH 03/14] Fix HTTP sig tweak on KeyId --- lib/pleroma/signature.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex index 0bf49fd7c..15bf3c317 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -15,7 +15,7 @@ defmodule Pleroma.Signature do |> Map.put(:fragment, nil) uri = - if String.ends_with?(uri.path, "/publickey") do + if not is_nil(uri.path) and String.ends_with?(uri.path, "/publickey") do Map.put(uri, :path, String.replace(uri.path, "/publickey", "")) else uri From 02dc651828af00ba88a687570833333d4b939c7e Mon Sep 17 00:00:00 2001 From: Sergey Suprunenko Date: Sun, 28 Jul 2019 20:24:39 +0000 Subject: [PATCH 04/14] Handle 303 redirects --- lib/pleroma/http/connection.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pleroma/http/connection.ex b/lib/pleroma/http/connection.ex index a1460d303..7e2c6f5e8 100644 --- a/lib/pleroma/http/connection.ex +++ b/lib/pleroma/http/connection.ex @@ -11,6 +11,7 @@ defmodule Pleroma.HTTP.Connection do connect_timeout: 10_000, recv_timeout: 20_000, follow_redirect: true, + force_redirect: true, pool: :federation ] @adapter Application.get_env(:tesla, :adapter) From 6a4b8b2681023dc355331999aeac6c24c5a21f7f Mon Sep 17 00:00:00 2001 From: Maksim Date: Sun, 28 Jul 2019 20:29:26 +0000 Subject: [PATCH 05/14] fixed User.update_and_set_cache for stale user --- lib/pleroma/user.ex | 2 +- test/user_test.exs | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 974f6df18..6e2fd3af8 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -471,7 +471,7 @@ defmodule Pleroma.User do end def update_and_set_cache(changeset) do - with {:ok, user} <- Repo.update(changeset) do + with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do set_cache(user) else e -> e diff --git a/test/user_test.exs b/test/user_test.exs index 8a7b7537f..556df45fd 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -1369,4 +1369,28 @@ defmodule Pleroma.UserTest do assert User.is_internal_user?(user) end end + + describe "update_and_set_cache/1" do + test "returns error when user is stale instead Ecto.StaleEntryError" do + user = insert(:user) + + changeset = Ecto.Changeset.change(user, bio: "test") + + Repo.delete(user) + + assert {:error, %Ecto.Changeset{errors: [id: {"is stale", [stale: true]}], valid?: false}} = + User.update_and_set_cache(changeset) + end + + test "performs update cache if user updated" do + user = insert(:user) + assert {:ok, nil} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}") + + changeset = Ecto.Changeset.change(user, bio: "test-bio") + + assert {:ok, %User{bio: "test-bio"} = user} = User.update_and_set_cache(changeset) + assert {:ok, user} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}") + assert %User{bio: "test-bio"} = User.get_cached_by_ap_id(user.ap_id) + end + end end From 242f5c585ed797917ba8c61ceb5d266f4c670c90 Mon Sep 17 00:00:00 2001 From: Sachin Joshi Date: Sun, 28 Jul 2019 20:30:10 +0000 Subject: [PATCH 06/14] add account confirmation email resend in mastodon api --- CHANGELOG.md | 1 + config/config.exs | 3 +- docs/api/pleroma_api.md | 8 ++++ .../mastodon_api/mastodon_api_controller.ex | 14 +++++++ lib/pleroma/web/router.ex | 6 +++ .../mastodon_api_controller_test.exs | 41 +++++++++++++++++++ 6 files changed, 72 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd3048b19..20f4eea41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - ActivityPub: Add an internal service actor for fetching ActivityPub objects. - ActivityPub: Optional signing of ActivityPub object fetches. - Admin API: Endpoint for fetching latest user's statuses +- Pleroma API: Add `/api/v1/pleroma/accounts/confirmation_resend?email=` for resending account confirmation. ### Changed - Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text diff --git a/config/config.exs b/config/config.exs index 569411866..17770640a 100644 --- a/config/config.exs +++ b/config/config.exs @@ -534,7 +534,8 @@ config :pleroma, :rate_limit, relation_id_action: {60_000, 2}, statuses_actions: {10_000, 15}, status_id_action: {60_000, 3}, - password_reset: {1_800_000, 5} + password_reset: {1_800_000, 5}, + account_confirmation_resend: {8_640_000, 5} # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. diff --git a/docs/api/pleroma_api.md b/docs/api/pleroma_api.md index d83ebd734..5698e88ac 100644 --- a/docs/api/pleroma_api.md +++ b/docs/api/pleroma_api.md @@ -245,6 +245,14 @@ See [Admin-API](Admin-API.md) - PATCH `/api/v1/pleroma/accounts/update_banner`: Set/clear user banner image - PATCH `/api/v1/pleroma/accounts/update_background`: Set/clear user background image +## `/api/v1/pleroma/accounts/confirmation_resend` +### Resend confirmation email +* Method `POST` +* Params: + * `email`: email of that needs to be verified +* Authentication: not required +* Response: 204 No Content + ## `/api/v1/pleroma/mascot` ### Gets user mascot image * Method `GET` diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index d660f3f05..5bdbb709b 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -4,6 +4,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [json_response: 3] + alias Ecto.Changeset alias Pleroma.Activity alias Pleroma.Bookmark @@ -74,6 +77,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do plug(RateLimiter, :app_account_creation when action == :account_register) plug(RateLimiter, :search when action in [:search, :search2, :account_search]) plug(RateLimiter, :password_reset when action == :password_reset) + plug(RateLimiter, :account_confirmation_resend when action == :account_confirmation_resend) @local_mastodon_name "Mastodon-Local" @@ -1839,6 +1843,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end + def account_confirmation_resend(conn, params) do + nickname_or_email = params["email"] || params["nickname"] + + with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email), + {:ok, _} <- User.try_send_confirmation_email(user) do + conn + |> json_response(:no_content, "") + end + end + def try_render(conn, target, params) when is_binary(target) do case render(conn, target, params) do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 47ee762dc..4e1ab6c33 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -412,6 +412,12 @@ defmodule Pleroma.Web.Router do get("/accounts/search", SearchController, :account_search) + post( + "/pleroma/accounts/confirmation_resend", + MastodonAPIController, + :account_confirmation_resend + ) + scope [] do pipe_through(:oauth_read_or_public) diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index ce2e44499..d7f92fac2 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -3923,4 +3923,45 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do assert conn.resp_body == "" end end + + describe "POST /api/v1/pleroma/accounts/confirmation_resend" do + setup do + setting = Pleroma.Config.get([:instance, :account_activation_required]) + + unless setting do + Pleroma.Config.put([:instance, :account_activation_required], true) + on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end) + end + + user = insert(:user) + info_change = User.Info.confirmation_changeset(user.info, need_confirmation: true) + + {:ok, user} = + user + |> Changeset.change() + |> Changeset.put_embed(:info, info_change) + |> Repo.update() + + assert user.info.confirmation_pending + + [user: user] + end + + test "resend account confirmation email", %{conn: conn, user: user} do + conn + |> assign(:user, user) + |> post("/api/v1/pleroma/accounts/confirmation_resend?email=#{user.email}") + |> json_response(:no_content) + + email = Pleroma.Emails.UserEmail.account_confirmation_email(user) + notify_email = Pleroma.Config.get([:instance, :notify_email]) + instance_name = Pleroma.Config.get([:instance, :name]) + + assert_email_sent( + from: {instance_name, notify_email}, + to: {user.name, user.email}, + html_body: email.html_body + ) + end + end end From 492d854e7aa29a2438dbbe2f95e509e43328eb7f Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 28 Jul 2019 21:29:10 +0000 Subject: [PATCH 07/14] transmogrifier: use User.delete() instead of handrolled user deletion code for remote users Closes #1104 --- CHANGELOG.md | 1 + .../web/activity_pub/transmogrifier.ex | 15 +---- test/notification_test.exs | 58 +++++++++++++++++++ 3 files changed, 60 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20f4eea41..48379b757 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Rich Media: Parser failing when no TTL can be found by image TTL setters - Rich Media: The crawled URL is now spliced into the rich media data. - ActivityPub S2S: sharedInbox usage has been mostly aligned with the rules in the AP specification. +- ActivityPub S2S: remote user deletions now work the same as local user deletions. ### Added - MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 602ae48e1..7f06e6edd 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -656,20 +656,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do nil -> case User.get_cached_by_ap_id(object_id) do %User{ap_id: ^actor} = user -> - {:ok, followers} = User.get_followers(user) - - Enum.each(followers, fn follower -> - User.unfollow(follower, user) - end) - - {:ok, friends} = User.get_friends(user) - - Enum.each(friends, fn followed -> - User.unfollow(user, followed) - end) - - User.invalidate_cache(user) - Repo.delete(user) + User.delete(user) nil -> :error diff --git a/test/notification_test.exs b/test/notification_test.exs index 28f8df49d..c88ac54bd 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -564,6 +564,64 @@ defmodule Pleroma.NotificationTest do assert Enum.empty?(Notification.for_user(user)) end + + test "notifications are deleted if a local user is deleted" do + user = insert(:user) + other_user = insert(:user) + + {:ok, _activity} = + CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}", "visibility" => "direct"}) + + refute Enum.empty?(Notification.for_user(other_user)) + + User.delete(user) + + assert Enum.empty?(Notification.for_user(other_user)) + end + + test "notifications are deleted if a remote user is deleted" do + remote_user = insert(:user) + local_user = insert(:user) + + dm_message = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "type" => "Create", + "actor" => remote_user.ap_id, + "id" => remote_user.ap_id <> "/activities/test", + "to" => [local_user.ap_id], + "cc" => [], + "object" => %{ + "type" => "Note", + "content" => "Hello!", + "tag" => [ + %{ + "type" => "Mention", + "href" => local_user.ap_id, + "name" => "@#{local_user.nickname}" + } + ], + "to" => [local_user.ap_id], + "cc" => [], + "attributedTo" => remote_user.ap_id + } + } + + {:ok, _dm_activity} = Transmogrifier.handle_incoming(dm_message) + + refute Enum.empty?(Notification.for_user(local_user)) + + delete_user_message = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "id" => remote_user.ap_id <> "/activities/delete", + "actor" => remote_user.ap_id, + "type" => "Delete", + "object" => remote_user.ap_id + } + + {:ok, _delete_activity} = Transmogrifier.handle_incoming(delete_user_message) + + assert Enum.empty?(Notification.for_user(local_user)) + end end describe "for_user" do From 9d78b3b281ff7f758d4e0dce19fd74d938e47ccc Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Mon, 29 Jul 2019 02:12:35 +0000 Subject: [PATCH 08/14] mix: add ex_const dependency --- mix.exs | 1 + mix.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/mix.exs b/mix.exs index e69940c5d..2a8fe2e9d 100644 --- a/mix.exs +++ b/mix.exs @@ -150,6 +150,7 @@ defmodule Pleroma.Mixfile do {:benchee, "~> 1.0"}, {:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)}, {:ex_rated, "~> 1.3"}, + {:ex_const, "~> 0.2"}, {:plug_static_index_html, "~> 1.0.0"}, {:excoveralls, "~> 0.11.1", only: :test}, {:mox, "~> 0.5", only: :test} diff --git a/mix.lock b/mix.lock index 5f20878d3..65da7be8b 100644 --- a/mix.lock +++ b/mix.lock @@ -27,6 +27,7 @@ "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"}, "ex_aws": {:hex, :ex_aws, "2.1.0", "b92651527d6c09c479f9013caa9c7331f19cba38a650590d82ebf2c6c16a1d8a", [:mix], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.0.1", "9e09366e77f25d3d88c5393824e613344631be8db0d1839faca49686e99b6704", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"}, + "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, "ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm"}, "ex_rated": {:hex, :ex_rated, "1.3.3", "30ecbdabe91f7eaa9d37fa4e81c85ba420f371babeb9d1910adbcd79ec798d27", [:mix], [{:ex2ms, "~> 1.5", [hex: :ex2ms, repo: "hexpm", optional: false]}], "hexpm"}, From b93498eb5289dc92587b77c316ed9f697bb9e5c8 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Mon, 29 Jul 2019 02:43:19 +0000 Subject: [PATCH 09/14] constants: add as_public constant and use it everywhere --- lib/mix/tasks/pleroma/database.ex | 12 ++++++--- lib/pleroma/activity/search.ex | 4 ++- lib/pleroma/constants.ex | 9 +++++++ lib/pleroma/web/activity_pub/activity_pub.ex | 27 +++++++------------ .../web/activity_pub/mrf/hellthread_policy.ex | 11 +++++--- .../web/activity_pub/mrf/keyword_policy.ex | 8 +++--- .../web/activity_pub/mrf/reject_non_public.ex | 6 ++--- .../web/activity_pub/mrf/simple_policy.ex | 12 ++++----- .../web/activity_pub/mrf/tag_policy.ex | 15 ++++++----- lib/pleroma/web/activity_pub/publisher.ex | 6 ++--- .../web/activity_pub/transmogrifier.ex | 11 ++++---- lib/pleroma/web/activity_pub/utils.ex | 13 ++++----- lib/pleroma/web/activity_pub/visibility.ex | 8 +++--- lib/pleroma/web/auth/authenticator.ex | 3 +-- lib/pleroma/web/common_api/common_api.ex | 3 +-- lib/pleroma/web/common_api/utils.ex | 5 ++-- .../mastodon_api/mastodon_api_controller.ex | 6 ++--- lib/pleroma/web/oauth/oauth_controller.ex | 3 +-- lib/pleroma/web/oauth/token.ex | 3 +-- .../web/ostatus/activity_representer.ex | 3 ++- .../web/ostatus/handlers/note_handler.ex | 5 ++-- .../rich_media/parsers/ttl/aws_signed_url.ex | 3 +-- lib/pleroma/web/twitter_api/twitter_api.ex | 4 ++- .../web/twitter_api/views/activity_view.ex | 3 ++- .../twitter_api/views/notification_view.ex | 4 ++- test/web/push/impl_test.exs | 6 ++--- 26 files changed, 104 insertions(+), 89 deletions(-) create mode 100644 lib/pleroma/constants.ex diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index e91fb31d1..8547a329a 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -8,6 +8,7 @@ defmodule Mix.Tasks.Pleroma.Database do alias Pleroma.Repo alias Pleroma.User require Logger + require Pleroma.Constants import Mix.Pleroma use Mix.Task @@ -99,10 +100,15 @@ defmodule Mix.Tasks.Pleroma.Database do NaiveDateTime.utc_now() |> NaiveDateTime.add(-(deadline * 86_400)) - public = "https://www.w3.org/ns/activitystreams#Public" - from(o in Object, - where: fragment("?->'to' \\? ? OR ?->'cc' \\? ?", o.data, ^public, o.data, ^public), + where: + fragment( + "?->'to' \\? ? OR ?->'cc' \\? ?", + o.data, + ^Pleroma.Constants.as_public(), + o.data, + ^Pleroma.Constants.as_public() + ), where: o.inserted_at < ^time_deadline, where: fragment("split_part(?->>'actor', '/', 3) != ?", o.data, ^Pleroma.Web.Endpoint.host()) diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex index 0cc3770a7..f847ac238 100644 --- a/lib/pleroma/activity/search.ex +++ b/lib/pleroma/activity/search.ex @@ -9,6 +9,8 @@ defmodule Pleroma.Activity.Search do alias Pleroma.User alias Pleroma.Web.ActivityPub.Visibility + require Pleroma.Constants + import Ecto.Query def search(user, search_query, options \\ []) do @@ -39,7 +41,7 @@ defmodule Pleroma.Activity.Search do defp restrict_public(q) do from([a, o] in q, where: fragment("?->>'type' = 'Create'", a.data), - where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients + where: ^Pleroma.Constants.as_public() in a.recipients ) end diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex new file mode 100644 index 000000000..ef1418543 --- /dev/null +++ b/lib/pleroma/constants.ex @@ -0,0 +1,9 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Constants do + use Const + + const(as_public, do: "https://www.w3.org/ns/activitystreams#Public") +end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index a42c50875..6fd7fef92 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -23,6 +23,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do import Pleroma.Web.ActivityPub.Visibility require Logger + require Pleroma.Constants # For Announce activities, we filter the recipients based on following status for any actors # that match actual users. See issue #164 for more information about why this is necessary. @@ -207,8 +208,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do def stream_out_participations(_, _), do: :noop def stream_out(activity) do - public = "https://www.w3.org/ns/activitystreams#Public" - if activity.data["type"] in ["Create", "Announce", "Delete"] do object = Object.normalize(activity) # Do not stream out poll replies @@ -216,7 +215,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do Pleroma.Web.Streamer.stream("user", activity) Pleroma.Web.Streamer.stream("list", activity) - if Enum.member?(activity.data["to"], public) do + if get_visibility(activity) == "public" do Pleroma.Web.Streamer.stream("public", activity) if activity.local do @@ -238,13 +237,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end else - # TODO: Write test, replace with visibility test - if !Enum.member?(activity.data["cc"] || [], public) && - !Enum.member?( - activity.data["to"], - User.get_cached_by_ap_id(activity.data["actor"]).follower_address - ), - do: Pleroma.Web.Streamer.stream("direct", activity) + if get_visibility(activity) == "direct", + do: Pleroma.Web.Streamer.stream("direct", activity) end end end @@ -514,7 +508,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end defp fetch_activities_for_context_query(context, opts) do - public = ["https://www.w3.org/ns/activitystreams#Public"] + public = [Pleroma.Constants.as_public()] recipients = if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public @@ -555,7 +549,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end def fetch_public_activities(opts \\ %{}) do - q = fetch_activities_query(["https://www.w3.org/ns/activitystreams#Public"], opts) + q = fetch_activities_query([Pleroma.Constants.as_public()], opts) q |> restrict_unlisted() @@ -646,10 +640,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp user_activities_recipients(%{"reading_user" => reading_user}) do if reading_user do - ["https://www.w3.org/ns/activitystreams#Public"] ++ - [reading_user.ap_id | reading_user.following] + [Pleroma.Constants.as_public()] ++ [reading_user.ap_id | reading_user.following] else - ["https://www.w3.org/ns/activitystreams#Public"] + [Pleroma.Constants.as_public()] end end @@ -834,7 +827,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do fragment( "not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)", activity.data, - ^["https://www.w3.org/ns/activitystreams#Public"] + ^[Pleroma.Constants.as_public()] ) ) end @@ -971,7 +964,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do where: fragment("? && ?", activity.recipients, ^recipients) or (fragment("? && ?", activity.recipients, ^recipients_with_public) and - "https://www.w3.org/ns/activitystreams#Public" in activity.recipients) + ^Pleroma.Constants.as_public() in activity.recipients) ) end diff --git a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex index a699f6a7e..377987cf2 100644 --- a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex @@ -4,6 +4,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do alias Pleroma.User + + require Pleroma.Constants + @moduledoc "Block messages with too much mentions (configurable)" @behaviour Pleroma.Web.ActivityPub.MRF @@ -19,12 +22,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do when follower_collection? and recipients > threshold -> message |> Map.put("to", [follower_collection]) - |> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"]) + |> Map.put("cc", [Pleroma.Constants.as_public()]) {:public, recipients} when recipients > threshold -> message |> Map.put("to", []) - |> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"]) + |> Map.put("cc", [Pleroma.Constants.as_public()]) _ -> message @@ -51,10 +54,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do recipients = (message["to"] || []) ++ (message["cc"] || []) follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address - if Enum.member?(recipients, "https://www.w3.org/ns/activitystreams#Public") do + if Enum.member?(recipients, Pleroma.Constants.as_public()) do recipients = recipients - |> List.delete("https://www.w3.org/ns/activitystreams#Public") + |> List.delete(Pleroma.Constants.as_public()) |> List.delete(follower_collection) {:public, length(recipients)} diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex index d5c341433..4eec8b916 100644 --- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex @@ -3,6 +3,8 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do + require Pleroma.Constants + @moduledoc "Reject or Word-Replace messages with a keyword or regex" @behaviour Pleroma.Web.ActivityPub.MRF @@ -31,12 +33,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do defp check_ftl_removal( %{"to" => to, "object" => %{"content" => content, "summary" => summary}} = message ) do - if "https://www.w3.org/ns/activitystreams#Public" in to and + if Pleroma.Constants.as_public() in to and Enum.any?(Pleroma.Config.get([:mrf_keyword, :federated_timeline_removal]), fn pattern -> string_matches?(content, pattern) or string_matches?(summary, pattern) end) do - to = List.delete(to, "https://www.w3.org/ns/activitystreams#Public") - cc = ["https://www.w3.org/ns/activitystreams#Public" | message["cc"] || []] + to = List.delete(to, Pleroma.Constants.as_public()) + cc = [Pleroma.Constants.as_public() | message["cc"] || []] message = message diff --git a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex index da13fd7c7..457b6ee10 100644 --- a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex +++ b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex @@ -10,7 +10,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do @behaviour Pleroma.Web.ActivityPub.MRF - @public "https://www.w3.org/ns/activitystreams#Public" + require Pleroma.Constants @impl true def filter(%{"type" => "Create"} = object) do @@ -19,8 +19,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do # Determine visibility visibility = cond do - @public in object["to"] -> "public" - @public in object["cc"] -> "unlisted" + Pleroma.Constants.as_public() in object["to"] -> "public" + Pleroma.Constants.as_public() in object["cc"] -> "unlisted" user.follower_address in object["to"] -> "followers" true -> "direct" end diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index 2cf63d3db..f266457e3 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -8,6 +8,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do @moduledoc "Filter activities depending on their origin instance" @behaviour MRF + require Pleroma.Constants + defp check_accept(%{host: actor_host} = _actor_info, object) do accepts = Pleroma.Config.get([:mrf_simple, :accept]) @@ -89,14 +91,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do object = with true <- MRF.subdomain_match?(timeline_removal, actor_host), user <- User.get_cached_by_ap_id(object["actor"]), - true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"] do - to = - List.delete(object["to"], "https://www.w3.org/ns/activitystreams#Public") ++ - [user.follower_address] + true <- Pleroma.Constants.as_public() in object["to"] do + to = List.delete(object["to"], Pleroma.Constants.as_public()) ++ [user.follower_address] - cc = - List.delete(object["cc"], user.follower_address) ++ - ["https://www.w3.org/ns/activitystreams#Public"] + cc = List.delete(object["cc"], user.follower_address) ++ [Pleroma.Constants.as_public()] object |> Map.put("to", to) diff --git a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex index b42c4ed76..70edf4f7f 100644 --- a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex @@ -19,7 +19,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do - `mrf_tag:disable-any-subscription`: Reject any follow requests """ - @public "https://www.w3.org/ns/activitystreams#Public" + require Pleroma.Constants defp get_tags(%User{tags: tags}) when is_list(tags), do: tags defp get_tags(_), do: [] @@ -70,9 +70,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do ) do user = User.get_cached_by_ap_id(actor) - if Enum.member?(to, @public) do - to = List.delete(to, @public) ++ [user.follower_address] - cc = List.delete(cc, user.follower_address) ++ [@public] + if Enum.member?(to, Pleroma.Constants.as_public()) do + to = List.delete(to, Pleroma.Constants.as_public()) ++ [user.follower_address] + cc = List.delete(cc, user.follower_address) ++ [Pleroma.Constants.as_public()] object = object @@ -103,9 +103,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do ) do user = User.get_cached_by_ap_id(actor) - if Enum.member?(to, @public) or Enum.member?(cc, @public) do - to = List.delete(to, @public) ++ [user.follower_address] - cc = List.delete(cc, @public) + if Enum.member?(to, Pleroma.Constants.as_public()) or + Enum.member?(cc, Pleroma.Constants.as_public()) do + to = List.delete(to, Pleroma.Constants.as_public()) ++ [user.follower_address] + cc = List.delete(cc, Pleroma.Constants.as_public()) object = object diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 016d78216..46edab0bd 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -11,6 +11,8 @@ defmodule Pleroma.Web.ActivityPub.Publisher do alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Transmogrifier + require Pleroma.Constants + import Pleroma.Web.ActivityPub.Visibility @behaviour Pleroma.Web.Federator.Publisher @@ -117,8 +119,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do |> Enum.map(& &1.ap_id) end - @as_public "https://www.w3.org/ns/activitystreams#Public" - defp maybe_use_sharedinbox(%User{info: %{source_data: data}}), do: (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"] @@ -145,7 +145,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do type == "Delete" -> maybe_use_sharedinbox(user) - @as_public in to || @as_public in cc -> + Pleroma.Constants.as_public() in to || Pleroma.Constants.as_public() in cc -> maybe_use_sharedinbox(user) length(to) + length(cc) > 1 -> diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 7f06e6edd..44bb1cb9a 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -19,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do import Ecto.Query require Logger + require Pleroma.Constants @doc """ Modifies an incoming AP object (mastodon format) to our internal format. @@ -102,8 +103,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do follower_collection = User.get_cached_by_ap_id(Containment.get_actor(object)).follower_address - explicit_mentions = - explicit_mentions ++ ["https://www.w3.org/ns/activitystreams#Public", follower_collection] + explicit_mentions = explicit_mentions ++ [Pleroma.Constants.as_public(), follower_collection] fix_explicit_addressing(object, explicit_mentions, follower_collection) end @@ -115,11 +115,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do if followers_collection not in recipients do cond do - "https://www.w3.org/ns/activitystreams#Public" in cc -> + Pleroma.Constants.as_public() in cc -> to = to ++ [followers_collection] Map.put(object, "to", to) - "https://www.w3.org/ns/activitystreams#Public" in to -> + Pleroma.Constants.as_public() in to -> cc = cc ++ [followers_collection] Map.put(object, "cc", cc) @@ -480,8 +480,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do {:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower), {:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]), - {_, false} <- - {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked}, + {_, false} <- {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked}, {_, false} <- {:user_locked, User.locked?(followed)}, {_, {:ok, follower}} <- {:follow, User.follow(follower, followed)}, {_, {:ok, _}} <- diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index c146f59d4..39074888b 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -18,6 +18,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do import Ecto.Query require Logger + require Pleroma.Constants @supported_object_types ["Article", "Note", "Video", "Page", "Question", "Answer"] @supported_report_states ~w(open closed resolved) @@ -418,7 +419,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do "type" => "Follow", "actor" => follower_id, "to" => [followed_id], - "cc" => ["https://www.w3.org/ns/activitystreams#Public"], + "cc" => [Pleroma.Constants.as_public()], "object" => followed_id, "state" => "pending" } @@ -510,7 +511,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do "actor" => ap_id, "object" => id, "to" => [user.follower_address, object.data["actor"]], - "cc" => ["https://www.w3.org/ns/activitystreams#Public"], + "cc" => [Pleroma.Constants.as_public()], "context" => object.data["context"] } @@ -530,7 +531,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do "actor" => ap_id, "object" => activity.data, "to" => [user.follower_address, activity.data["actor"]], - "cc" => ["https://www.w3.org/ns/activitystreams#Public"], + "cc" => [Pleroma.Constants.as_public()], "context" => context } @@ -547,7 +548,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do "actor" => ap_id, "object" => activity.data, "to" => [user.follower_address, activity.data["actor"]], - "cc" => ["https://www.w3.org/ns/activitystreams#Public"], + "cc" => [Pleroma.Constants.as_public()], "context" => context } @@ -556,7 +557,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do def add_announce_to_object( %Activity{ - data: %{"actor" => actor, "cc" => ["https://www.w3.org/ns/activitystreams#Public"]} + data: %{"actor" => actor, "cc" => [Pleroma.Constants.as_public()]} }, object ) do @@ -765,7 +766,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do ) do cc = Map.get(data, "cc", []) follower_address = User.get_cached_by_ap_id(data["actor"]).follower_address - public = "https://www.w3.org/ns/activitystreams#Public" + public = Pleroma.Constants.as_public() case visibility do "public" -> diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index 097fceb08..dfb166b65 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -8,14 +8,14 @@ defmodule Pleroma.Web.ActivityPub.Visibility do alias Pleroma.Repo alias Pleroma.User - @public "https://www.w3.org/ns/activitystreams#Public" + require Pleroma.Constants @spec is_public?(Object.t() | Activity.t() | map()) :: boolean() def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false def is_public?(%Object{data: data}), do: is_public?(data) def is_public?(%Activity{data: data}), do: is_public?(data) def is_public?(%{"directMessage" => true}), do: false - def is_public?(data), do: @public in (data["to"] ++ (data["cc"] || [])) + def is_public?(data), do: Pleroma.Constants.as_public() in (data["to"] ++ (data["cc"] || [])) def is_private?(activity) do with false <- is_public?(activity), @@ -73,10 +73,10 @@ defmodule Pleroma.Web.ActivityPub.Visibility do cc = object.data["cc"] || [] cond do - @public in to -> + Pleroma.Constants.as_public() in to -> "public" - @public in cc -> + Pleroma.Constants.as_public() in cc -> "unlisted" # this should use the sql for the object's activity diff --git a/lib/pleroma/web/auth/authenticator.ex b/lib/pleroma/web/auth/authenticator.ex index d4e0ffa80..dd49987f7 100644 --- a/lib/pleroma/web/auth/authenticator.ex +++ b/lib/pleroma/web/auth/authenticator.ex @@ -21,8 +21,7 @@ defmodule Pleroma.Web.Auth.Authenticator do def create_from_registration(plug, registration), do: implementation().create_from_registration(plug, registration) - @callback get_registration(Plug.Conn.t()) :: - {:ok, Registration.t()} | {:error, any()} + @callback get_registration(Plug.Conn.t()) :: {:ok, Registration.t()} | {:error, any()} def get_registration(plug), do: implementation().get_registration(plug) @callback handle_error(Plug.Conn.t(), any()) :: any() diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 44af6a773..2db58324b 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -300,8 +300,7 @@ defmodule Pleroma.Web.CommonAPI do } } = activity <- get_by_id_or_ap_id(id_or_ap_id), true <- Visibility.is_public?(activity), - %{valid?: true} = info_changeset <- - User.Info.add_pinnned_activity(user.info, activity), + %{valid?: true} = info_changeset <- User.Info.add_pinnned_activity(user.info, activity), changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset), {:ok, _user} <- User.update_and_set_cache(changeset) do diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 94462c3dd..d80fffa26 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -19,6 +19,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do alias Pleroma.Web.MediaProxy require Logger + require Pleroma.Constants # This is a hack for twidere. def get_by_id_or_ap_id(id) do @@ -66,7 +67,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do @spec get_to_and_cc(User.t(), list(String.t()), Activity.t() | nil, String.t()) :: {list(String.t()), list(String.t())} def get_to_and_cc(user, mentioned_users, inReplyTo, "public") do - to = ["https://www.w3.org/ns/activitystreams#Public" | mentioned_users] + to = [Pleroma.Constants.as_public() | mentioned_users] cc = [user.follower_address] if inReplyTo do @@ -78,7 +79,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do def get_to_and_cc(user, mentioned_users, inReplyTo, "unlisted") do to = [user.follower_address | mentioned_users] - cc = ["https://www.w3.org/ns/activitystreams#Public"] + cc = [Pleroma.Constants.as_public()] if inReplyTo do {Enum.uniq([inReplyTo.data["actor"] | to]), cc} diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 5bdbb709b..174e93468 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -49,6 +49,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do import Ecto.Query require Logger + require Pleroma.Constants @rate_limited_relations_actions ~w(follow unfollow)a @@ -1224,10 +1225,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do recipients = if for_user do - ["https://www.w3.org/ns/activitystreams#Public"] ++ - [for_user.ap_id | for_user.following] + [Pleroma.Constants.as_public()] ++ [for_user.ap_id | for_user.following] else - ["https://www.w3.org/ns/activitystreams#Public"] + [Pleroma.Constants.as_public()] end activities = diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index ef53b7ae3..81eae2c8b 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -365,8 +365,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "connect"} = params) do with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn), %Registration{} = registration <- Repo.get(Registration, registration_id), - {_, {:ok, auth}} <- - {:create_authorization, do_create_authorization(conn, params)}, + {_, {:ok, auth}} <- {:create_authorization, do_create_authorization(conn, params)}, %User{} = user <- Repo.preload(auth, :user).user, {:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do conn diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex index 90c304487..40f131b57 100644 --- a/lib/pleroma/web/oauth/token.ex +++ b/lib/pleroma/web/oauth/token.ex @@ -44,8 +44,7 @@ defmodule Pleroma.Web.OAuth.Token do |> Repo.find_resource() end - @spec exchange_token(App.t(), Authorization.t()) :: - {:ok, Token.t()} | {:error, Changeset.t()} + @spec exchange_token(App.t(), Authorization.t()) :: {:ok, Token.t()} | {:error, Changeset.t()} def exchange_token(app, auth) do with {:ok, auth} <- Authorization.use_token(auth), true <- auth.app_id == app.id do diff --git a/lib/pleroma/web/ostatus/activity_representer.ex b/lib/pleroma/web/ostatus/activity_representer.ex index 95037125d..760345301 100644 --- a/lib/pleroma/web/ostatus/activity_representer.ex +++ b/lib/pleroma/web/ostatus/activity_representer.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do alias Pleroma.Web.OStatus.UserRepresenter require Logger + require Pleroma.Constants defp get_href(id) do with %Object{data: %{"external_url" => external_url}} <- Object.get_cached_by_ap_id(id) do @@ -34,7 +35,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do Enum.map(to, fn id -> cond do # Special handling for the AP/Ostatus public collections - "https://www.w3.org/ns/activitystreams#Public" == id -> + Pleroma.Constants.as_public() == id -> {:link, [ rel: "mentioned", diff --git a/lib/pleroma/web/ostatus/handlers/note_handler.ex b/lib/pleroma/web/ostatus/handlers/note_handler.ex index 8e0adad91..3005e8f57 100644 --- a/lib/pleroma/web/ostatus/handlers/note_handler.ex +++ b/lib/pleroma/web/ostatus/handlers/note_handler.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do require Logger + require Pleroma.Constants alias Pleroma.Activity alias Pleroma.Object @@ -49,7 +50,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do def get_collection_mentions(entry) do transmogrify = fn "http://activityschema.org/collection/public" -> - "https://www.w3.org/ns/activitystreams#Public" + Pleroma.Constants.as_public() group -> group @@ -126,7 +127,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do to <- make_to_list(actor, mentions), date <- XML.string_from_xpath("//published", entry), unlisted <- XML.string_from_xpath("//mastodon:scope", entry) == "unlisted", - cc <- if(unlisted, do: ["https://www.w3.org/ns/activitystreams#Public"], else: []), + cc <- if(unlisted, do: [Pleroma.Constants.as_public()], else: []), note <- CommonAPI.Utils.make_note_data( actor.ap_id, diff --git a/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex b/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex index 014c0935f..0dc1efdaf 100644 --- a/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex +++ b/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex @@ -19,8 +19,7 @@ defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do defp is_aws_signed_url(image) when is_binary(image) do %URI{host: host, query: query} = URI.parse(image) - if String.contains?(host, "amazonaws.com") and - String.contains?(query, "X-Amz-Expires") do + if String.contains?(host, "amazonaws.com") and String.contains?(query, "X-Amz-Expires") do image else nil diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index bb5dda204..80082ea84 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -15,6 +15,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do import Ecto.Query + require Pleroma.Constants + def create_status(%User{} = user, %{"status" => _} = data) do CommonAPI.post(user, data) end @@ -286,7 +288,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do from( [a, o] in Activity.with_preloaded_object(Activity), where: fragment("?->>'type' = 'Create'", a.data), - where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients, + where: ^Pleroma.Constants.as_public() in a.recipients, where: fragment( "to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)", diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex index e84af84dc..abae63877 100644 --- a/lib/pleroma/web/twitter_api/views/activity_view.ex +++ b/lib/pleroma/web/twitter_api/views/activity_view.ex @@ -19,6 +19,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do import Ecto.Query require Logger + require Pleroma.Constants defp query_context_ids([]), do: [] @@ -91,7 +92,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do String.ends_with?(ap_id, "/followers") -> nil - ap_id == "https://www.w3.org/ns/activitystreams#Public" -> + ap_id == Pleroma.Constants.as_public() -> nil user = User.get_cached_by_ap_id(ap_id) -> diff --git a/lib/pleroma/web/twitter_api/views/notification_view.ex b/lib/pleroma/web/twitter_api/views/notification_view.ex index e7c7a7496..085cd5aa3 100644 --- a/lib/pleroma/web/twitter_api/views/notification_view.ex +++ b/lib/pleroma/web/twitter_api/views/notification_view.ex @@ -10,6 +10,8 @@ defmodule Pleroma.Web.TwitterAPI.NotificationView do alias Pleroma.Web.TwitterAPI.ActivityView alias Pleroma.Web.TwitterAPI.UserView + require Pleroma.Constants + defp get_user(ap_id, opts) do cond do user = opts[:users][ap_id] -> @@ -18,7 +20,7 @@ defmodule Pleroma.Web.TwitterAPI.NotificationView do String.ends_with?(ap_id, "/followers") -> nil - ap_id == "https://www.w3.org/ns/activitystreams#Public" -> + ap_id == Pleroma.Constants.as_public() -> nil true -> diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs index 1e948086a..e2f89f40a 100644 --- a/test/web/push/impl_test.exs +++ b/test/web/push/impl_test.exs @@ -124,8 +124,7 @@ defmodule Pleroma.Web.Push.ImplTest do {:ok, _, _, activity} = CommonAPI.follow(user, other_user) object = Object.normalize(activity) - assert Impl.format_body(%{activity: activity}, user, object) == - "@Bob has followed you" + assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has followed you" end test "renders body for announce activity" do @@ -156,7 +155,6 @@ defmodule Pleroma.Web.Push.ImplTest do {:ok, activity, _} = CommonAPI.favorite(activity.id, user) object = Object.normalize(activity) - assert Impl.format_body(%{activity: activity}, user, object) == - "@Bob has favorited your post" + assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has favorited your post" end end From 159bbec570c308bf3d71487726737a91eb569178 Mon Sep 17 00:00:00 2001 From: Maksim Date: Mon, 29 Jul 2019 05:02:20 +0000 Subject: [PATCH 10/14] added tests for OstatusController --- lib/pleroma/web/ostatus/ostatus_controller.ex | 170 +++-- test/web/ostatus/ostatus_controller_test.exs | 630 ++++++++++++++---- 2 files changed, 585 insertions(+), 215 deletions(-) diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex index 372d52899..c70063b84 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/ostatus/ostatus_controller.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do use Pleroma.Web, :controller + alias Fallback.RedirectController alias Pleroma.Activity alias Pleroma.Object alias Pleroma.User @@ -12,42 +13,44 @@ defmodule Pleroma.Web.OStatus.OStatusController do alias Pleroma.Web.ActivityPub.ActivityPubController alias Pleroma.Web.ActivityPub.ObjectView alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.Endpoint alias Pleroma.Web.Federator + alias Pleroma.Web.Metadata.PlayerView alias Pleroma.Web.OStatus alias Pleroma.Web.OStatus.ActivityRepresenter alias Pleroma.Web.OStatus.FeedRepresenter + alias Pleroma.Web.Router alias Pleroma.Web.XML plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming]) + plug( + Pleroma.Plugs.SetFormatPlug + when action in [:feed_redirect, :object, :activity, :notice] + ) + action_fallback(:errors) + def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do + with {_, %User{} = user} <- + {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do + RedirectController.redirector_with_meta(conn, %{user: user}) + end + end + + def feed_redirect(%{assigns: %{format: format}} = conn, _params) + when format in ["json", "activity+json"] do + ActivityPubController.call(conn, :user) + end + def feed_redirect(conn, %{"nickname" => nickname}) do - case get_format(conn) do - "html" -> - with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do - Fallback.RedirectController.redirector_with_meta(conn, %{user: user}) - else - nil -> {:error, :not_found} - end - - "activity+json" -> - ActivityPubController.call(conn, :user) - - "json" -> - ActivityPubController.call(conn, :user) - - _ -> - with %User{} = user <- User.get_cached_by_nickname(nickname) do - redirect(conn, external: OStatus.feed_path(user)) - else - nil -> {:error, :not_found} - end + with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do + redirect(conn, external: OStatus.feed_path(user)) end end def feed(conn, %{"nickname" => nickname} = params) do - with %User{} = user <- User.get_cached_by_nickname(nickname) do + with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do query_params = Map.take(params, ["max_id"]) |> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id}) @@ -65,8 +68,6 @@ defmodule Pleroma.Web.OStatus.OStatusController do conn |> put_resp_content_type("application/atom+xml") |> send_resp(200, response) - else - nil -> {:error, :not_found} end end @@ -97,93 +98,82 @@ defmodule Pleroma.Web.OStatus.OStatusController do |> send_resp(200, "") end - def object(conn, %{"uuid" => uuid}) do - if get_format(conn) in ["activity+json", "json"] do - ActivityPubController.call(conn, :object) - else - with id <- o_status_url(conn, :object, uuid), - {_, %Activity{} = activity} <- - {:activity, Activity.get_create_by_object_ap_id_with_object(id)}, - {_, true} <- {:public?, Visibility.is_public?(activity)}, - %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do - case get_format(conn) do - "html" -> redirect(conn, to: "/notice/#{activity.id}") - _ -> represent_activity(conn, nil, activity, user) - end - else - {:public?, false} -> - {:error, :not_found} + def object(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid}) + when format in ["json", "activity+json"] do + ActivityPubController.call(conn, :object) + end - {:activity, nil} -> - {:error, :not_found} - - e -> - e + def object(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do + with id <- o_status_url(conn, :object, uuid), + {_, %Activity{} = activity} <- + {:activity, Activity.get_create_by_object_ap_id_with_object(id)}, + {_, true} <- {:public?, Visibility.is_public?(activity)}, + %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do + case format do + "html" -> redirect(conn, to: "/notice/#{activity.id}") + _ -> represent_activity(conn, nil, activity, user) end + else + reason when reason in [{:public?, false}, {:activity, nil}] -> + {:error, :not_found} + + e -> + e end end - def activity(conn, %{"uuid" => uuid}) do - if get_format(conn) in ["activity+json", "json"] do - ActivityPubController.call(conn, :activity) - else - with id <- o_status_url(conn, :activity, uuid), - {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)}, - {_, true} <- {:public?, Visibility.is_public?(activity)}, - %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do - case format = get_format(conn) do - "html" -> redirect(conn, to: "/notice/#{activity.id}") - _ -> represent_activity(conn, format, activity, user) - end - else - {:public?, false} -> - {:error, :not_found} + def activity(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid}) + when format in ["json", "activity+json"] do + ActivityPubController.call(conn, :activity) + end - {:activity, nil} -> - {:error, :not_found} - - e -> - e + def activity(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do + with id <- o_status_url(conn, :activity, uuid), + {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)}, + {_, true} <- {:public?, Visibility.is_public?(activity)}, + %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do + case format do + "html" -> redirect(conn, to: "/notice/#{activity.id}") + _ -> represent_activity(conn, format, activity, user) end + else + reason when reason in [{:public?, false}, {:activity, nil}] -> + {:error, :not_found} + + e -> + e end end - def notice(conn, %{"id" => id}) do + def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)}, {_, true} <- {:public?, Visibility.is_public?(activity)}, %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do - case format = get_format(conn) do - "html" -> - if activity.data["type"] == "Create" do - %Object{} = object = Object.normalize(activity) + cond do + format == "html" && activity.data["type"] == "Create" -> + %Object{} = object = Object.normalize(activity) - Fallback.RedirectController.redirector_with_meta(conn, %{ + RedirectController.redirector_with_meta( + conn, + %{ activity_id: activity.id, object: object, - url: - Pleroma.Web.Router.Helpers.o_status_url( - Pleroma.Web.Endpoint, - :notice, - activity.id - ), + url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id), user: user - }) - else - Fallback.RedirectController.redirector(conn, nil) - end + } + ) - _ -> + format == "html" -> + RedirectController.redirector(conn, nil) + + true -> represent_activity(conn, format, activity, user) end else - {:public?, false} -> + reason when reason in [{:public?, false}, {:activity, nil}] -> conn |> put_status(404) - |> Fallback.RedirectController.redirector(nil, 404) - - {:activity, nil} -> - conn - |> Fallback.RedirectController.redirector(nil, 404) + |> RedirectController.redirector(nil, 404) e -> e @@ -204,13 +194,13 @@ defmodule Pleroma.Web.OStatus.OStatusController do "content-security-policy", "default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;" ) - |> put_view(Pleroma.Web.Metadata.PlayerView) + |> put_view(PlayerView) |> render("player.html", url) else _error -> conn |> put_status(404) - |> Fallback.RedirectController.redirector(nil, 404) + |> RedirectController.redirector(nil, 404) end end @@ -248,6 +238,8 @@ defmodule Pleroma.Web.OStatus.OStatusController do render_error(conn, :not_found, "Not found") end + def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found}) + def errors(conn, _) do render_error(conn, :internal_server_error, "Something went wrong") end diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/web/ostatus/ostatus_controller_test.exs index bb7648bdd..9f756effb 100644 --- a/test/web/ostatus/ostatus_controller_test.exs +++ b/test/web/ostatus/ostatus_controller_test.exs @@ -101,160 +101,538 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do assert response(conn, 404) end - test "gets an object", %{conn: conn} do - note_activity = insert(:note_activity) - object = Object.normalize(note_activity) - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) - url = "/objects/#{uuid}" + describe "GET object/2" do + test "gets an object", %{conn: conn} do + note_activity = insert(:note_activity) + object = Object.normalize(note_activity) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) + [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) + url = "/objects/#{uuid}" + + conn = + conn + |> put_req_header("accept", "application/xml") + |> get(url) + + expected = + ActivityRepresenter.to_simple_form(note_activity, user, true) + |> ActivityRepresenter.wrap_with_entry() + |> :xmerl.export_simple(:xmerl_xml) + |> to_string + + assert response(conn, 200) == expected + end + + test "redirects to /notice/id for html format", %{conn: conn} do + note_activity = insert(:note_activity) + object = Object.normalize(note_activity) + [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) + url = "/objects/#{uuid}" + + conn = + conn + |> put_req_header("accept", "text/html") + |> get(url) + + assert redirected_to(conn) == "/notice/#{note_activity.id}" + end + + test "500s when user not found", %{conn: conn} do + note_activity = insert(:note_activity) + object = Object.normalize(note_activity) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) + User.invalidate_cache(user) + Pleroma.Repo.delete(user) + [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) + url = "/objects/#{uuid}" + + conn = + conn + |> put_req_header("accept", "application/xml") + |> get(url) + + assert response(conn, 500) == ~S({"error":"Something went wrong"}) + end + + test "404s on private objects", %{conn: conn} do + note_activity = insert(:direct_note_activity) + object = Object.normalize(note_activity) + [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) + + conn + |> get("/objects/#{uuid}") + |> response(404) + end + + test "404s on nonexisting objects", %{conn: conn} do + conn + |> get("/objects/123") + |> response(404) + end + end + + describe "GET activity/2" do + test "gets an activity in xml format", %{conn: conn} do + note_activity = insert(:note_activity) + [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) - conn = conn |> put_req_header("accept", "application/xml") - |> get(url) + |> get("/activities/#{uuid}") + |> response(200) + end - expected = - ActivityRepresenter.to_simple_form(note_activity, user, true) - |> ActivityRepresenter.wrap_with_entry() - |> :xmerl.export_simple(:xmerl_xml) - |> to_string + test "redirects to /notice/id for html format", %{conn: conn} do + note_activity = insert(:note_activity) + [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) - assert response(conn, 200) == expected + conn = + conn + |> put_req_header("accept", "text/html") + |> get("/activities/#{uuid}") + + assert redirected_to(conn) == "/notice/#{note_activity.id}" + end + + test "505s when user not found", %{conn: conn} do + note_activity = insert(:note_activity) + [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) + User.invalidate_cache(user) + Pleroma.Repo.delete(user) + + conn = + conn + |> put_req_header("accept", "text/html") + |> get("/activities/#{uuid}") + + assert response(conn, 500) == ~S({"error":"Something went wrong"}) + end + + test "404s on deleted objects", %{conn: conn} do + note_activity = insert(:note_activity) + object = Object.normalize(note_activity) + [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) + + conn + |> put_req_header("accept", "application/xml") + |> get("/objects/#{uuid}") + |> response(200) + + Object.delete(object) + + conn + |> put_req_header("accept", "application/xml") + |> get("/objects/#{uuid}") + |> response(404) + end + + test "404s on private activities", %{conn: conn} do + note_activity = insert(:direct_note_activity) + [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) + + conn + |> get("/activities/#{uuid}") + |> response(404) + end + + test "404s on nonexistent activities", %{conn: conn} do + conn + |> get("/activities/123") + |> response(404) + end + + test "gets an activity in AS2 format", %{conn: conn} do + note_activity = insert(:note_activity) + [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) + url = "/activities/#{uuid}" + + conn = + conn + |> put_req_header("accept", "application/activity+json") + |> get(url) + + assert json_response(conn, 200) + end end - test "404s on private objects", %{conn: conn} do - note_activity = insert(:direct_note_activity) - object = Object.normalize(note_activity) - [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) + describe "GET notice/2" do + test "gets a notice in xml format", %{conn: conn} do + note_activity = insert(:note_activity) - conn - |> get("/objects/#{uuid}") - |> response(404) - end + conn + |> get("/notice/#{note_activity.id}") + |> response(200) + end - test "404s on nonexisting objects", %{conn: conn} do - conn - |> get("/objects/123") - |> response(404) - end + test "gets a notice in AS2 format", %{conn: conn} do + note_activity = insert(:note_activity) - test "gets an activity in xml format", %{conn: conn} do - note_activity = insert(:note_activity) - [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) - - conn - |> put_req_header("accept", "application/xml") - |> get("/activities/#{uuid}") - |> response(200) - end - - test "404s on deleted objects", %{conn: conn} do - note_activity = insert(:note_activity) - object = Object.normalize(note_activity) - [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) - - conn - |> put_req_header("accept", "application/xml") - |> get("/objects/#{uuid}") - |> response(200) - - Object.delete(object) - - conn - |> put_req_header("accept", "application/xml") - |> get("/objects/#{uuid}") - |> response(404) - end - - test "404s on private activities", %{conn: conn} do - note_activity = insert(:direct_note_activity) - [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) - - conn - |> get("/activities/#{uuid}") - |> response(404) - end - - test "404s on nonexistent activities", %{conn: conn} do - conn - |> get("/activities/123") - |> response(404) - end - - test "gets a notice in xml format", %{conn: conn} do - note_activity = insert(:note_activity) - - conn - |> get("/notice/#{note_activity.id}") - |> response(200) - end - - test "gets a notice in AS2 format", %{conn: conn} do - note_activity = insert(:note_activity) - - conn - |> put_req_header("accept", "application/activity+json") - |> get("/notice/#{note_activity.id}") - |> json_response(200) - end - - test "only gets a notice in AS2 format for Create messages", %{conn: conn} do - note_activity = insert(:note_activity) - url = "/notice/#{note_activity.id}" - - conn = conn |> put_req_header("accept", "application/activity+json") - |> get(url) + |> get("/notice/#{note_activity.id}") + |> json_response(200) + end - assert json_response(conn, 200) + test "500s when actor not found", %{conn: conn} do + note_activity = insert(:note_activity) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) + User.invalidate_cache(user) + Pleroma.Repo.delete(user) - user = insert(:user) + conn = + conn + |> get("/notice/#{note_activity.id}") - {:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user) - url = "/notice/#{like_activity.id}" + assert response(conn, 500) == ~S({"error":"Something went wrong"}) + end - assert like_activity.data["type"] == "Like" + test "only gets a notice in AS2 format for Create messages", %{conn: conn} do + note_activity = insert(:note_activity) + url = "/notice/#{note_activity.id}" - conn = - build_conn() - |> put_req_header("accept", "application/activity+json") - |> get(url) + conn = + conn + |> put_req_header("accept", "application/activity+json") + |> get(url) - assert response(conn, 404) + assert json_response(conn, 200) + + user = insert(:user) + + {:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user) + url = "/notice/#{like_activity.id}" + + assert like_activity.data["type"] == "Like" + + conn = + build_conn() + |> put_req_header("accept", "application/activity+json") + |> get(url) + + assert response(conn, 404) + end + + test "render html for redirect for html format", %{conn: conn} do + note_activity = insert(:note_activity) + + resp = + conn + |> put_req_header("accept", "text/html") + |> get("/notice/#{note_activity.id}") + |> response(200) + + assert resp =~ + "" + + user = insert(:user) + + {:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user) + + assert like_activity.data["type"] == "Like" + + resp = + conn + |> put_req_header("accept", "text/html") + |> get("/notice/#{like_activity.id}") + |> response(200) + + assert resp =~ "" + end + + test "404s a private notice", %{conn: conn} do + note_activity = insert(:direct_note_activity) + url = "/notice/#{note_activity.id}" + + conn = + conn + |> get(url) + + assert response(conn, 404) + end + + test "404s a nonexisting notice", %{conn: conn} do + url = "/notice/123" + + conn = + conn + |> get(url) + + assert response(conn, 404) + end end - test "gets an activity in AS2 format", %{conn: conn} do - note_activity = insert(:note_activity) - [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) - url = "/activities/#{uuid}" + describe "feed_redirect" do + test "undefined format. it redirects to feed", %{conn: conn} do + note_activity = insert(:note_activity) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) - conn = - conn - |> put_req_header("accept", "application/activity+json") - |> get(url) + response = + conn + |> put_req_header("accept", "application/xml") + |> get("/users/#{user.nickname}") + |> response(302) - assert json_response(conn, 200) + assert response == + "You are being redirected." + end + + test "undefined format. it returns error when user not found", %{conn: conn} do + response = + conn + |> put_req_header("accept", "application/xml") + |> get("/users/jimm") + |> response(404) + + assert response == ~S({"error":"Not found"}) + end + + test "activity+json format. it redirects on actual feed of user", %{conn: conn} do + note_activity = insert(:note_activity) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) + + response = + conn + |> put_req_header("accept", "application/activity+json") + |> get("/users/#{user.nickname}") + |> json_response(200) + + assert response["endpoints"] == %{ + "oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize", + "oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps", + "oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token", + "sharedInbox" => "#{Pleroma.Web.base_url()}/inbox" + } + + assert response["@context"] == [ + "https://www.w3.org/ns/activitystreams", + "http://localhost:4001/schemas/litepub-0.1.jsonld", + %{"@language" => "und"} + ] + + assert Map.take(response, [ + "followers", + "following", + "id", + "inbox", + "manuallyApprovesFollowers", + "name", + "outbox", + "preferredUsername", + "summary", + "tag", + "type", + "url" + ]) == %{ + "followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers", + "following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following", + "id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}", + "inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox", + "manuallyApprovesFollowers" => false, + "name" => user.name, + "outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox", + "preferredUsername" => user.nickname, + "summary" => user.bio, + "tag" => [], + "type" => "Person", + "url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}" + } + end + + test "activity+json format. it returns error whe use not found", %{conn: conn} do + response = + conn + |> put_req_header("accept", "application/activity+json") + |> get("/users/jimm") + |> json_response(404) + + assert response == "Not found" + end + + test "json format. it redirects on actual feed of user", %{conn: conn} do + note_activity = insert(:note_activity) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) + + response = + conn + |> put_req_header("accept", "application/json") + |> get("/users/#{user.nickname}") + |> json_response(200) + + assert response["endpoints"] == %{ + "oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize", + "oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps", + "oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token", + "sharedInbox" => "#{Pleroma.Web.base_url()}/inbox" + } + + assert response["@context"] == [ + "https://www.w3.org/ns/activitystreams", + "http://localhost:4001/schemas/litepub-0.1.jsonld", + %{"@language" => "und"} + ] + + assert Map.take(response, [ + "followers", + "following", + "id", + "inbox", + "manuallyApprovesFollowers", + "name", + "outbox", + "preferredUsername", + "summary", + "tag", + "type", + "url" + ]) == %{ + "followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers", + "following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following", + "id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}", + "inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox", + "manuallyApprovesFollowers" => false, + "name" => user.name, + "outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox", + "preferredUsername" => user.nickname, + "summary" => user.bio, + "tag" => [], + "type" => "Person", + "url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}" + } + end + + test "json format. it returns error whe use not found", %{conn: conn} do + response = + conn + |> put_req_header("accept", "application/json") + |> get("/users/jimm") + |> json_response(404) + + assert response == "Not found" + end + + test "html format. it redirects on actual feed of user", %{conn: conn} do + note_activity = insert(:note_activity) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) + + response = + conn + |> get("/users/#{user.nickname}") + |> response(200) + + assert response == + Fallback.RedirectController.redirector_with_meta( + conn, + %{user: user} + ).resp_body + end + + test "html format. it returns error when user not found", %{conn: conn} do + response = + conn + |> get("/users/jimm") + |> json_response(404) + + assert response == %{"error" => "Not found"} + end end - test "404s a private notice", %{conn: conn} do - note_activity = insert(:direct_note_activity) - url = "/notice/#{note_activity.id}" + describe "GET /notice/:id/embed_player" do + test "render embed player", %{conn: conn} do + note_activity = insert(:note_activity) + object = Pleroma.Object.normalize(note_activity) - conn = - conn - |> get(url) + object_data = + Map.put(object.data, "attachment", [ + %{ + "url" => [ + %{ + "href" => + "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", + "mediaType" => "video/mp4", + "type" => "Link" + } + ] + } + ]) - assert response(conn, 404) - end + object + |> Ecto.Changeset.change(data: object_data) + |> Pleroma.Repo.update() - test "404s a nonexisting notice", %{conn: conn} do - url = "/notice/123" + conn = + conn + |> get("/notice/#{note_activity.id}/embed_player") - conn = - conn - |> get(url) + assert Plug.Conn.get_resp_header(conn, "x-frame-options") == ["ALLOW"] - assert response(conn, 404) + assert Plug.Conn.get_resp_header( + conn, + "content-security-policy" + ) == [ + "default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;" + ] + + assert response(conn, 200) =~ + "" + end + + test "404s when activity isn't create", %{conn: conn} do + note_activity = insert(:note_activity, data_attrs: %{"type" => "Like"}) + + assert conn + |> get("/notice/#{note_activity.id}/embed_player") + |> response(404) + end + + test "404s when activity is direct message", %{conn: conn} do + note_activity = insert(:note_activity, data_attrs: %{"directMessage" => true}) + + assert conn + |> get("/notice/#{note_activity.id}/embed_player") + |> response(404) + end + + test "404s when attachment is empty", %{conn: conn} do + note_activity = insert(:note_activity) + object = Pleroma.Object.normalize(note_activity) + object_data = Map.put(object.data, "attachment", []) + + object + |> Ecto.Changeset.change(data: object_data) + |> Pleroma.Repo.update() + + assert conn + |> get("/notice/#{note_activity.id}/embed_player") + |> response(404) + end + + test "404s when attachment isn't audio or video", %{conn: conn} do + note_activity = insert(:note_activity) + object = Pleroma.Object.normalize(note_activity) + + object_data = + Map.put(object.data, "attachment", [ + %{ + "url" => [ + %{ + "href" => "https://peertube.moe/static/webseed/480.jpg", + "mediaType" => "image/jpg", + "type" => "Link" + } + ] + } + ]) + + object + |> Ecto.Changeset.change(data: object_data) + |> Pleroma.Repo.update() + + assert conn + |> get("/notice/#{note_activity.id}/embed_player") + |> response(404) + end end end From c0e258cf21395fa2d5338ee238e4fcf4f3b3bf30 Mon Sep 17 00:00:00 2001 From: Sergey Suprunenko Date: Mon, 29 Jul 2019 16:17:22 +0000 Subject: [PATCH 11/14] Redirect not logged-in users to the MastoFE login page on private instances --- CHANGELOG.md | 1 + lib/pleroma/web/router.ex | 2 +- .../mastodon_api/mastodon_api_controller_test.exs | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48379b757..5416d452e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Rich Media: The crawled URL is now spliced into the rich media data. - ActivityPub S2S: sharedInbox usage has been mostly aligned with the rules in the AP specification. - ActivityPub S2S: remote user deletions now work the same as local user deletions. +- Not being able to access the Mastodon FE login page on private instances ### Added - MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 4e1ab6c33..0689d69fb 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -698,7 +698,7 @@ defmodule Pleroma.Web.Router do post("/auth/password", MastodonAPIController, :password_reset) scope [] do - pipe_through(:oauth_read_or_public) + pipe_through(:oauth_read) get("/web/*path", MastodonAPIController, :index) end end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index d7f92fac2..66016c886 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -3154,6 +3154,21 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do assert redirected_to(conn) == "/web/login" end + test "redirects not logged-in users to the login page on private instances", %{ + conn: conn, + path: path + } do + is_public = Pleroma.Config.get([:instance, :public]) + Pleroma.Config.put([:instance, :public], false) + + conn = get(conn, path) + + assert conn.status == 302 + assert redirected_to(conn) == "/web/login" + + Pleroma.Config.put([:instance, :public], is_public) + end + test "does not redirect logged in users to the login page", %{conn: conn, path: path} do token = insert(:oauth_token) From 0bee2131ce55ffd702ddc92800499b01b86d3765 Mon Sep 17 00:00:00 2001 From: Eugenij Date: Mon, 29 Jul 2019 16:17:40 +0000 Subject: [PATCH 12/14] Add `mailerEnabled` to the NodeInfo metadata --- CHANGELOG.md | 1 + lib/pleroma/web/nodeinfo/nodeinfo_controller.ex | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5416d452e..e77fe4f3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text - Federation: Return 403 errors when trying to request pages from a user's follower/following collections if they have `hide_followers`/`hide_follows` set - NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option +- NodeInfo: Return `mailerEnabled` in `metadata` - Mastodon API: Unsubscribe followers when they unfollow a user - AdminAPI: Add "godmode" while fetching user statuses (i.e. admin can see private statuses) diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index a1d7fcc7d..54f89e65c 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -165,6 +165,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do }, accountActivationRequired: Config.get([:instance, :account_activation_required], false), invitesEnabled: Config.get([:instance, :invites_enabled], false), + mailerEnabled: Config.get([Pleroma.Emails.Mailer, :enabled], false), features: features, restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]), skipThreadContainment: Config.get([:instance, :skip_thread_containment], false) From 5795a890e9d14a9e51e2613d26620899b2171623 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Mon, 29 Jul 2019 19:09:58 +0000 Subject: [PATCH 13/14] markdown: clean up html generated by earmark --- lib/pleroma/web/common_api/utils.ex | 3 +++ test/web/common_api/common_api_utils_test.exs | 10 +++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index d80fffa26..6d42ae8ae 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -300,6 +300,9 @@ defmodule Pleroma.Web.CommonAPI.Utils do |> Earmark.as_html!() |> Formatter.linkify(options) |> Formatter.html_escape("text/html") + |> (fn {text, mentions, tags} -> + {String.replace(text, ~r/\r?\n/, ""), mentions, tags} + end).() end def make_note_data( diff --git a/test/web/common_api/common_api_utils_test.exs b/test/web/common_api/common_api_utils_test.exs index af320f31f..38b2319ac 100644 --- a/test/web/common_api/common_api_utils_test.exs +++ b/test/web/common_api/common_api_utils_test.exs @@ -99,14 +99,14 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do test "works for bare text/markdown" do text = "**hello world**" - expected = "

hello world

\n" + expected = "

hello world

" {output, [], []} = Utils.format_input(text, "text/markdown") assert output == expected text = "**hello world**\n\n*another paragraph*" - expected = "

hello world

\n

another paragraph

\n" + expected = "

hello world

another paragraph

" {output, [], []} = Utils.format_input(text, "text/markdown") @@ -118,7 +118,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do by someone """ - expected = "

cool quote

\n
\n

by someone

\n" + expected = "

cool quote

by someone

" {output, [], []} = Utils.format_input(text, "text/markdown") @@ -157,11 +157,11 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do text = "**hello world**\n\n*another @user__test and @user__test google.com paragraph*" expected = - "

hello world

\n

another hello world

another @user__test and @user__test google.com paragraph

\n" + }\" class=\"u-url mention\" href=\"http://foo.com/user__test\">@user__test google.com paragraph

" {output, _, _} = Utils.format_input(text, "text/markdown") From 5835069215b880ad261a006cf310da624a82ca4a Mon Sep 17 00:00:00 2001 From: kaniini Date: Mon, 29 Jul 2019 19:42:26 +0000 Subject: [PATCH 14/14] Revert "Merge branch 'bugfix/clean-up-markdown-rendering' into 'develop'" This reverts merge request !1504 --- lib/pleroma/web/common_api/utils.ex | 3 --- test/web/common_api/common_api_utils_test.exs | 10 +++++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 6d42ae8ae..d80fffa26 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -300,9 +300,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do |> Earmark.as_html!() |> Formatter.linkify(options) |> Formatter.html_escape("text/html") - |> (fn {text, mentions, tags} -> - {String.replace(text, ~r/\r?\n/, ""), mentions, tags} - end).() end def make_note_data( diff --git a/test/web/common_api/common_api_utils_test.exs b/test/web/common_api/common_api_utils_test.exs index 38b2319ac..af320f31f 100644 --- a/test/web/common_api/common_api_utils_test.exs +++ b/test/web/common_api/common_api_utils_test.exs @@ -99,14 +99,14 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do test "works for bare text/markdown" do text = "**hello world**" - expected = "

hello world

" + expected = "

hello world

\n" {output, [], []} = Utils.format_input(text, "text/markdown") assert output == expected text = "**hello world**\n\n*another paragraph*" - expected = "

hello world

another paragraph

" + expected = "

hello world

\n

another paragraph

\n" {output, [], []} = Utils.format_input(text, "text/markdown") @@ -118,7 +118,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do by someone """ - expected = "

cool quote

by someone

" + expected = "

cool quote

\n
\n

by someone

\n" {output, [], []} = Utils.format_input(text, "text/markdown") @@ -157,11 +157,11 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do text = "**hello world**\n\n*another @user__test and @user__test google.com paragraph*" expected = - "

hello world

another hello world

\n

another @user__test and @user__test google.com paragraph

" + }\" class=\"u-url mention\" href=\"http://foo.com/user__test\">@user__test google.com paragraph

\n" {output, _, _} = Utils.format_input(text, "text/markdown")