From f672555ad347861617c9b9b2c323b5044e840649 Mon Sep 17 00:00:00 2001 From: href Date: Sun, 16 Dec 2018 17:15:07 +0100 Subject: [PATCH 1/5] Upgrade to Phoenix 1.4 --- lib/pleroma/web/channels/user_socket.ex | 4 - lib/pleroma/web/endpoint.ex | 2 +- .../web/mastodon_api/mastodon_socket.ex | 7 - .../web/twitter_api/twitter_api_controller.ex | 126 +++++++++++++----- mix.exs | 7 +- mix.lock | 10 +- .../web_finger/web_finger_controller_test.exs | 4 - 7 files changed, 102 insertions(+), 58 deletions(-) diff --git a/lib/pleroma/web/channels/user_socket.ex b/lib/pleroma/web/channels/user_socket.ex index 07ddee169..9918d3b49 100644 --- a/lib/pleroma/web/channels/user_socket.ex +++ b/lib/pleroma/web/channels/user_socket.ex @@ -6,10 +6,6 @@ defmodule Pleroma.Web.UserSocket do # channel "room:*", Pleroma.Web.RoomChannel channel("chat:*", Pleroma.Web.ChatChannel) - ## Transports - transport(:websocket, Phoenix.Transports.WebSocket) - # transport :longpoll, Phoenix.Transports.LongPoll - # Socket params are passed from the client and can # be used to verify and authenticate a user. After # verification, you can put default assigns into diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index c5f9d51d9..aacc88195 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -3,7 +3,7 @@ defmodule Pleroma.Web.Endpoint do socket("/socket", Pleroma.Web.UserSocket) - socket("/api/v1", Pleroma.Web.MastodonAPI.MastodonSocket) + socket("/api/v1", Pleroma.Web.MastodonAPI.MastodonSocket, websocket: [path: "/streaming"]) # Serve at "/" the static files from "priv/static" directory. # diff --git a/lib/pleroma/web/mastodon_api/mastodon_socket.ex b/lib/pleroma/web/mastodon_api/mastodon_socket.ex index 755ac5730..1b75897b5 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_socket.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_socket.ex @@ -4,13 +4,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonSocket do alias Pleroma.Web.OAuth.Token alias Pleroma.{User, Repo} - transport( - :websocket, - Phoenix.Transports.WebSocket.Raw, - # We never receive data. - timeout: :infinity - ) - @spec connect(params :: map(), Phoenix.Socket.t()) :: {:ok, Phoenix.Socket.t()} | :error def connect(%{"access_token" => token} = params, socket) do with %Token{user_id: user_id} <- Repo.get_by(Token, token: token), diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 38eff8191..327620302 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -17,7 +17,10 @@ defmodule Pleroma.Web.TwitterAPI.Controller do def verify_credentials(%{assigns: %{user: user}} = conn, _params) do token = Phoenix.Token.sign(conn, "user socket", user.id) - render(conn, UserView, "show.json", %{user: user, token: token}) + + conn + |> put_view(UserView) + |> render("show.json", %{user: user, token: token}) end def status_update(%{assigns: %{user: user}} = conn, %{"status" => _} = status_data) do @@ -58,7 +61,8 @@ def public_and_external_timeline(%{assigns: %{user: user}} = conn, params) do activities = ActivityPub.fetch_public_activities(params) conn - |> render(ActivityView, "index.json", %{activities: activities, for: user}) + |> put_view(ActivityView) + |> render("index.json", %{activities: activities, for: user}) end def public_timeline(%{assigns: %{user: user}} = conn, params) do @@ -71,7 +75,8 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do activities = ActivityPub.fetch_public_activities(params) conn - |> render(ActivityView, "index.json", %{activities: activities, for: user}) + |> put_view(ActivityView) + |> render("index.json", %{activities: activities, for: user}) end def friends_timeline(%{assigns: %{user: user}} = conn, params) do @@ -86,16 +91,22 @@ def friends_timeline(%{assigns: %{user: user}} = conn, params) do |> ActivityPub.contain_timeline(user) conn - |> render(ActivityView, "index.json", %{activities: activities, for: user}) + |> put_view(ActivityView) + |> render("index.json", %{activities: activities, for: user}) end def show_user(conn, params) do with {:ok, shown} <- TwitterAPI.get_user(params) do - if user = conn.assigns.user do - render(conn, UserView, "show.json", %{user: shown, for: user}) - else - render(conn, UserView, "show.json", %{user: shown}) - end + params = + if user = conn.assigns.user do + %{user: shown, for: user} + else + %{user: shown} + end + + conn + |> put_view(UserView) + |> render("show.json", params) else {:error, msg} -> bad_request_reply(conn, msg) @@ -108,7 +119,8 @@ def user_timeline(%{assigns: %{user: user}} = conn, params) do activities = ActivityPub.fetch_user_activities(target_user, user, params) conn - |> render(ActivityView, "index.json", %{activities: activities, for: user}) + |> put_view(ActivityView) + |> render("index.json", %{activities: activities, for: user}) {:error, msg} -> bad_request_reply(conn, msg) @@ -124,7 +136,8 @@ def mentions_timeline(%{assigns: %{user: user}} = conn, params) do activities = ActivityPub.fetch_activities([user.ap_id], params) conn - |> render(ActivityView, "index.json", %{activities: activities, for: user}) + |> put_view(ActivityView) + |> render("index.json", %{activities: activities, for: user}) end def dm_timeline(%{assigns: %{user: user}} = conn, params) do @@ -137,14 +150,16 @@ def dm_timeline(%{assigns: %{user: user}} = conn, params) do activities = Repo.all(query) conn - |> render(ActivityView, "index.json", %{activities: activities, for: user}) + |> put_view(ActivityView) + |> render("index.json", %{activities: activities, for: user}) end def notifications(%{assigns: %{user: user}} = conn, params) do notifications = Notification.for_user(user, params) conn - |> render(NotificationView, "notification.json", %{notifications: notifications, for: user}) + |> put_view(NotificationView) + |> render("notification.json", %{notifications: notifications, for: user}) end def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest_id} = params) do @@ -153,7 +168,8 @@ def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest notifications = Notification.for_user(user, params) conn - |> render(NotificationView, "notification.json", %{notifications: notifications, for: user}) + |> put_view(NotificationView) + |> render("notification.json", %{notifications: notifications, for: user}) end def notifications_read(%{assigns: %{user: _user}} = conn, _) do @@ -163,7 +179,9 @@ def notifications_read(%{assigns: %{user: _user}} = conn, _) do def follow(%{assigns: %{user: user}} = conn, params) do case TwitterAPI.follow(user, params) do {:ok, user, followed, _activity} -> - render(conn, UserView, "show.json", %{user: followed, for: user}) + conn + |> put_view(UserView) + |> render("show.json", %{user: followed, for: user}) {:error, msg} -> forbidden_json_reply(conn, msg) @@ -173,7 +191,9 @@ def follow(%{assigns: %{user: user}} = conn, params) do def block(%{assigns: %{user: user}} = conn, params) do case TwitterAPI.block(user, params) do {:ok, user, blocked} -> - render(conn, UserView, "show.json", %{user: blocked, for: user}) + conn + |> put_view(UserView) + |> render("show.json", %{user: blocked, for: user}) {:error, msg} -> forbidden_json_reply(conn, msg) @@ -183,7 +203,9 @@ def block(%{assigns: %{user: user}} = conn, params) do def unblock(%{assigns: %{user: user}} = conn, params) do case TwitterAPI.unblock(user, params) do {:ok, user, blocked} -> - render(conn, UserView, "show.json", %{user: blocked, for: user}) + conn + |> put_view(UserView) + |> render("show.json", %{user: blocked, for: user}) {:error, msg} -> forbidden_json_reply(conn, msg) @@ -192,14 +214,18 @@ def unblock(%{assigns: %{user: user}} = conn, params) do def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do with {:ok, activity} <- TwitterAPI.delete(user, id) do - render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) + conn + |> put_view(ActivityView) + |> render("activity.json", %{activity: activity, for: user}) end end def unfollow(%{assigns: %{user: user}} = conn, params) do case TwitterAPI.unfollow(user, params) do {:ok, user, unfollowed} -> - render(conn, UserView, "show.json", %{user: unfollowed, for: user}) + conn + |> put_view(UserView) + |> render("show.json", %{user: unfollowed, for: user}) {:error, msg} -> forbidden_json_reply(conn, msg) @@ -209,7 +235,9 @@ def unfollow(%{assigns: %{user: user}} = conn, params) do def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Activity{} = activity <- Repo.get(Activity, id), true <- ActivityPub.visible_for_user?(activity, user) do - render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) + conn + |> put_view(ActivityView) + |> render("activity.json", %{activity: activity, for: user}) end end @@ -223,7 +251,8 @@ def fetch_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do "user" => user }) do conn - |> render(ActivityView, "index.json", %{activities: activities, for: user}) + |> put_view(ActivityView) + |> render("index.json", %{activities: activities, for: user}) end end @@ -290,34 +319,44 @@ def get_by_id_or_ap_id(id) do def favorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)}, {:ok, activity} <- TwitterAPI.fav(user, id) do - render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) + conn + |> put_view(ActivityView) + |> render("activity.json", %{activity: activity, for: user}) end end def unfavorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)}, {:ok, activity} <- TwitterAPI.unfav(user, id) do - render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) + conn + |> put_view(ActivityView) + |> render("activity.json", %{activity: activity, for: user}) end end def retweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)}, {:ok, activity} <- TwitterAPI.repeat(user, id) do - render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) + conn + |> put_view(ActivityView) + |> render("activity.json", %{activity: activity, for: user}) end end def unretweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)}, {:ok, activity} <- TwitterAPI.unrepeat(user, id) do - render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) + conn + |> put_view(ActivityView) + |> render("activity.json", %{activity: activity, for: user}) end end def register(conn, params) do with {:ok, user} <- TwitterAPI.register_user(params) do - render(conn, UserView, "show.json", %{user: user}) + conn + |> put_view(UserView) + |> render("show.json", %{user: user}) else {:error, errors} -> conn @@ -339,7 +378,9 @@ def update_avatar(%{assigns: %{user: user}} = conn, params) do {:ok, user} = User.update_and_set_cache(change) CommonAPI.update(user) - render(conn, UserView, "show.json", %{user: user, for: user}) + conn + |> put_view(UserView) + |> render("show.json", %{user: user, for: user}) end def update_banner(%{assigns: %{user: user}} = conn, params) do @@ -394,7 +435,9 @@ def followers(%{assigns: %{user: for_user}} = conn, params) do true -> followers end - render(conn, UserView, "index.json", %{users: followers, for: conn.assigns[:user]}) + conn + |> put_view(UserView) + |> render("index.json", %{users: followers, for: conn.assigns[:user]}) else _e -> bad_request_reply(conn, "Can't get followers") end @@ -410,7 +453,9 @@ def friends(%{assigns: %{user: for_user}} = conn, params) do true -> friends end - render(conn, UserView, "index.json", %{users: friends, for: conn.assigns[:user]}) + conn + |> put_view(UserView) + |> render("index.json", %{users: friends, for: conn.assigns[:user]}) else _e -> bad_request_reply(conn, "Can't get friends") end @@ -419,7 +464,9 @@ def friends(%{assigns: %{user: for_user}} = conn, params) do def friend_requests(conn, params) do with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params), {:ok, friend_requests} <- User.get_follow_requests(user) do - render(conn, UserView, "index.json", %{users: friend_requests, for: conn.assigns[:user]}) + conn + |> put_view(UserView) + |> render("index.json", %{users: friend_requests, for: conn.assigns[:user]}) else _e -> bad_request_reply(conn, "Can't get friend requests") end @@ -439,7 +486,9 @@ def approve_friend_request(conn, %{"user_id" => uid} = _params) do object: follow_activity.data["id"], type: "Accept" }) do - render(conn, UserView, "show.json", %{user: follower, for: followed}) + conn + |> put_view(UserView) + |> render("show.json", %{user: follower, for: followed}) else e -> bad_request_reply(conn, "Can't approve user: #{inspect(e)}") end @@ -458,7 +507,9 @@ def deny_friend_request(conn, %{"user_id" => uid} = _params) do object: follow_activity.data["id"], type: "Reject" }) do - render(conn, UserView, "show.json", %{user: follower, for: followed}) + conn + |> put_view(UserView) + |> render("show.json", %{user: follower, for: followed}) else e -> bad_request_reply(conn, "Can't deny user: #{inspect(e)}") end @@ -522,7 +573,10 @@ def update_profile(%{assigns: %{user: user}} = conn, params) do changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng), {:ok, user} <- User.update_and_set_cache(changeset) do CommonAPI.update(user) - render(conn, UserView, "user.json", %{user: user, for: user}) + + conn + |> put_view(UserView) + |> render("user.json", %{user: user, for: user}) else error -> Logger.debug("Can't update user: #{inspect(error)}") @@ -534,14 +588,16 @@ def search(%{assigns: %{user: user}} = conn, %{"q" => _query} = params) do activities = TwitterAPI.search(user, params) conn - |> render(ActivityView, "index.json", %{activities: activities, for: user}) + |> put_view(ActivityView) + |> render("index.json", %{activities: activities, for: user}) end def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do users = User.search(query, true) conn - |> render(UserView, "index.json", %{users: users, for: user}) + |> put_view(UserView) + |> render("index.json", %{users: users, for: user}) end defp bad_request_reply(conn, error_message) do diff --git a/mix.exs b/mix.exs index 5b56b9490..52974c841 100644 --- a/mix.exs +++ b/mix.exs @@ -43,12 +43,13 @@ defp elixirc_paths(_), do: ["lib"] # Type `mix help deps` for examples and options. defp deps do [ - {:phoenix, "~> 1.3.3"}, - {:phoenix_pubsub, "~> 1.0.2"}, + # Until Phoenix 1.4.1 is released + {:phoenix, github: "feld/phoenix", branch: "v1.4"}, + {:plug_cowboy, "~> 1.0"}, + {:phoenix_pubsub, "~> 1.1"}, {:phoenix_ecto, "~> 3.3"}, {:postgrex, ">= 0.13.5"}, {:gettext, "~> 0.15"}, - {:cowboy, "~> 1.1.2", override: true}, {:comeonin, "~> 4.1.1"}, {:pbkdf2_elixir, "~> 0.12.3"}, {:trailing_format_plug, "~> 0.0.7"}, diff --git a/mix.lock b/mix.lock index 6f8ca5bb0..0a3b6f715 100644 --- a/mix.lock +++ b/mix.lock @@ -32,7 +32,7 @@ "makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, "meck": {:hex, :meck, "0.8.9", "64c5c0bd8bcca3a180b44196265c8ed7594e16bcc845d0698ec6b4e577f48188", [:rebar3], [], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, - "mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"}, + "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, "mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"}, "mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, @@ -40,11 +40,13 @@ "nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"}, - "phoenix": {:hex, :phoenix, "1.3.4", "aaa1b55e5523083a877bcbe9886d9ee180bf2c8754905323493c2ac325903dc5", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, + "phoenix": {:git, "https://github.com/feld/phoenix.git", "38e03a6c18eabe24284d9bf1e1dbc09fd24547ad", [branch: "v1.4"]}, "phoenix_ecto": {:hex, :phoenix_ecto, "3.3.0", "702f6e164512853d29f9d20763493f2b3bcfcb44f118af2bc37bb95d0801b480", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_html": {:hex, :phoenix_html, "2.11.2", "86ebd768258ba60a27f5578bec83095bdb93485d646fc4111db8844c316602d6", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.2", "bfa7fd52788b5eaa09cb51ff9fcad1d9edfeb68251add458523f839392f034c1", [:mix], [], "hexpm"}, - "plug": {:hex, :plug, "1.6.2", "e06a7bd2bb6de5145da0dd950070110dce88045351224bd98e84edfdaaf5ffee", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.1", "6668d787e602981f24f17a5fbb69cc98f8ab085114ebfac6cc36e10a90c8e93c", [:mix], [], "hexpm"}, + "plug": {:hex, :plug, "1.7.1", "8516d565fb84a6a8b2ca722e74e2cd25ca0fc9d64f364ec9dbec09d33eb78ccd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"}, + "plug_cowboy": {:hex, :plug_cowboy, "1.0.0", "2e2a7d3409746d335f451218b8bb0858301c3de6d668c3052716c909936eb57a", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, + "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}, "postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, diff --git a/test/web/web_finger/web_finger_controller_test.exs b/test/web/web_finger/web_finger_controller_test.exs index cac003e76..423e9d135 100644 --- a/test/web/web_finger/web_finger_controller_test.exs +++ b/test/web/web_finger/web_finger_controller_test.exs @@ -1,11 +1,7 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do use Pleroma.Web.ConnCase - alias Pleroma.User - alias Pleroma.Web.WebFinger.WebFingerController - import Pleroma.Factory - import ExUnit.CaptureLog import Tesla.Mock setup do From 5dcb7aeceacc61beb99466c086ea26b5ae8d947a Mon Sep 17 00:00:00 2001 From: href Date: Sun, 16 Dec 2018 17:49:42 +0100 Subject: [PATCH 2/5] More put_view. --- lib/pleroma/plugs/federating_plug.ex | 3 +- .../mastodon_api/mastodon_api_controller.ex | 127 +++++++++++++----- 2 files changed, 95 insertions(+), 35 deletions(-) diff --git a/lib/pleroma/plugs/federating_plug.ex b/lib/pleroma/plugs/federating_plug.ex index f0442ca15..b5326d97b 100644 --- a/lib/pleroma/plugs/federating_plug.ex +++ b/lib/pleroma/plugs/federating_plug.ex @@ -11,7 +11,8 @@ def call(conn, _opts) do else conn |> put_status(404) - |> Phoenix.Controller.render(Pleroma.Web.ErrorView, "404.json") + |> Phoenix.Controller.put_view(Pleroma.Web.ErrorView) + |> Phoenix.Controller.render("404.json") |> halt() end end diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 726807f0a..665b75437 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -226,7 +226,8 @@ def home_timeline(%{assigns: %{user: user}} = conn, params) do conn |> add_link_headers(:home_timeline, activities) - |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity}) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) end def public_timeline(%{assigns: %{user: user}} = conn, params) do @@ -244,7 +245,8 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do conn |> add_link_headers(:public_timeline, activities, false, %{"local" => local_only}) - |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity}) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) end def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do @@ -259,7 +261,8 @@ def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do conn |> add_link_headers(:user_statuses, activities, params["id"]) - |> render(StatusView, "index.json", %{ + |> put_view(StatusView) + |> render("index.json", %{ activities: activities, for: reading_user, as: :activity @@ -278,13 +281,16 @@ def dm_timeline(%{assigns: %{user: user}} = conn, params) do conn |> add_link_headers(:dm_timeline, activities) - |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity}) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) end def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Activity{} = activity <- Repo.get(Activity, id), true <- ActivityPub.visible_for_user?(activity, user) do - try_render(conn, StatusView, "status.json", %{activity: activity, for: user}) + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user}) end end @@ -347,7 +353,9 @@ def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do {:ok, activity} = Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ -> CommonAPI.post(user, params) end) - try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user, as: :activity}) end def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do @@ -363,28 +371,36 @@ def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user) do - try_render(conn, StatusView, "status.json", %{activity: announce, for: user, as: :activity}) + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: announce, for: user, as: :activity}) end end def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user), %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do - try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user, as: :activity}) end end def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user), %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do - try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user, as: :activity}) end end def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user), %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do - try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user, as: :activity}) end end @@ -433,7 +449,10 @@ def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do id = List.wrap(id) q = from(u in User, where: u.id in ^id) targets = Repo.all(q) - render(conn, AccountView, "relationships.json", %{user: user, targets: targets}) + + conn + |> put_view(AccountView) + |> render("relationships.json", %{user: user, targets: targets}) end # Instead of returning a 400 when no "id" params is present, Mastodon returns an empty array. @@ -452,7 +471,10 @@ def update_media(%{assigns: %{user: user}} = conn, data) do |> Repo.update() attachment_data = Map.put(new_data, "id", object.id) - render(conn, StatusView, "attachment.json", %{attachment: attachment_data}) + + conn + |> put_view(StatusView) + |> render("attachment.json", %{attachment: attachment_data}) end end @@ -463,7 +485,10 @@ def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do description: Map.get(data, "description") ) do attachment_data = Map.put(object.data, "id", object.id) - render(conn, StatusView, "attachment.json", %{attachment: attachment_data}) + + conn + |> put_view(StatusView) + |> render("attachment.json", %{attachment: attachment_data}) end end @@ -471,7 +496,10 @@ def favourited_by(conn, %{"id" => id}) do with %Activity{data: %{"object" => %{"likes" => likes}}} <- Repo.get(Activity, id) do q = from(u in User, where: u.ap_id in ^likes) users = Repo.all(q) - render(conn, AccountView, "accounts.json", %{users: users, as: :user}) + + conn + |> put_view(AccountView) + |> render(AccountView, "accounts.json", %{users: users, as: :user}) else _ -> json(conn, []) end @@ -481,7 +509,10 @@ def reblogged_by(conn, %{"id" => id}) do with %Activity{data: %{"object" => %{"announcements" => announces}}} <- Repo.get(Activity, id) do q = from(u in User, where: u.ap_id in ^announces) users = Repo.all(q) - render(conn, AccountView, "accounts.json", %{users: users, as: :user}) + + conn + |> put_view(AccountView) + |> render("accounts.json", %{users: users, as: :user}) else _ -> json(conn, []) end @@ -503,7 +534,8 @@ def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do conn |> add_link_headers(:hashtag_timeline, activities, params["tag"], %{"local" => local_only}) - |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity}) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) end def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do @@ -516,7 +548,9 @@ def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do true -> followers end - render(conn, AccountView, "accounts.json", %{users: followers, as: :user}) + conn + |> put_view(AccountView) + |> render("accounts.json", %{users: followers, as: :user}) end end @@ -530,13 +564,17 @@ def following(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do true -> followers end - render(conn, AccountView, "accounts.json", %{users: followers, as: :user}) + conn + |> put_view(AccountView) + |> render("accounts.json", %{users: followers, as: :user}) end end def follow_requests(%{assigns: %{user: followed}} = conn, _params) do with {:ok, follow_requests} <- User.get_follow_requests(followed) do - render(conn, AccountView, "accounts.json", %{users: follow_requests, as: :user}) + conn + |> put_view(AccountView) + |> render("accounts.json", %{users: follow_requests, as: :user}) end end @@ -552,7 +590,9 @@ def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id} object: follow_activity.data["id"], type: "Accept" }) do - render(conn, AccountView, "relationship.json", %{user: followed, target: follower}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: followed, target: follower}) else {:error, message} -> conn @@ -572,7 +612,9 @@ def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) d object: follow_activity.data["id"], type: "Reject" }) do - render(conn, AccountView, "relationship.json", %{user: followed, target: follower}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: followed, target: follower}) else {:error, message} -> conn @@ -591,7 +633,9 @@ def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do follower, followed ) do - render(conn, AccountView, "relationship.json", %{user: follower, target: followed}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: follower, target: followed}) else {:error, message} -> conn @@ -604,7 +648,9 @@ def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do with %User{} = followed <- Repo.get_by(User, nickname: uri), {:ok, follower} <- User.maybe_direct_follow(follower, followed), {:ok, _activity} <- ActivityPub.follow(follower, followed) do - render(conn, AccountView, "account.json", %{user: followed, for: follower}) + conn + |> put_view(AccountView) + |> render("account.json", %{user: followed, for: follower}) else {:error, message} -> conn @@ -617,7 +663,9 @@ def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do with %User{} = followed <- Repo.get(User, id), {:ok, _activity} <- ActivityPub.unfollow(follower, followed), {:ok, follower, _} <- User.unfollow(follower, followed) do - render(conn, AccountView, "relationship.json", %{user: follower, target: followed}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: follower, target: followed}) end end @@ -625,7 +673,9 @@ def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do with %User{} = blocked <- Repo.get(User, id), {:ok, blocker} <- User.block(blocker, blocked), {:ok, _activity} <- ActivityPub.block(blocker, blocked) do - render(conn, AccountView, "relationship.json", %{user: blocker, target: blocked}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: blocker, target: blocked}) else {:error, message} -> conn @@ -638,7 +688,9 @@ def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do with %User{} = blocked <- Repo.get(User, id), {:ok, blocker} <- User.unblock(blocker, blocked), {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do - render(conn, AccountView, "relationship.json", %{user: blocker, target: blocked}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: blocker, target: blocked}) else {:error, message} -> conn @@ -763,7 +815,8 @@ def favourites(%{assigns: %{user: user}} = conn, _) do |> Enum.reverse() conn - |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity}) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) end def get_lists(%{assigns: %{user: user}} = conn, opts) do @@ -831,7 +884,9 @@ def remove_from_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_id def list_accounts(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Pleroma.List{} = list <- Pleroma.List.get(id, user), {:ok, users} = Pleroma.List.get_following(list) do - render(conn, AccountView, "accounts.json", %{users: users, as: :user}) + conn + |> put_view(AccountView) + |> render("accounts.json", %{users: users, as: :user}) end end @@ -864,7 +919,8 @@ def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) |> Enum.reverse() conn - |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity}) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) else _e -> conn @@ -968,7 +1024,8 @@ def index(%{assigns: %{user: user}} = conn, _params) do conn |> put_layout(false) - |> render(MastodonView, "index.html", %{initial_state: initial_state}) + |> put_view(MastodonView) + |> render("index.html", %{initial_state: initial_state}) else conn |> redirect(to: "/web/login") @@ -1041,7 +1098,9 @@ def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do Logger.debug("Unimplemented, returning unmodified relationship") with %User{} = target <- Repo.get(User, id) do - render(conn, AccountView, "relationship.json", %{user: user, target: target}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: user, target: target}) end end @@ -1242,9 +1301,9 @@ def suggestions(%{assigns: %{user: user}} = conn, _) do end end - def try_render(conn, renderer, target, params) + def try_render(conn, target, params) when is_binary(target) do - res = render(conn, renderer, target, params) + res = render(conn, target, params) if res == nil do conn @@ -1255,7 +1314,7 @@ def try_render(conn, renderer, target, params) end end - def try_render(conn, _, _, _) do + def try_render(conn, _, _) do conn |> put_status(501) |> json(%{error: "Can't display this activity"}) From 77b5154c825e2ff5996f170c03657eaadcef4680 Mon Sep 17 00:00:00 2001 From: href Date: Mon, 17 Dec 2018 14:39:59 +0100 Subject: [PATCH 3/5] Cowboy handler for Mastodon WebSocket --- config/config.exs | 9 ++ lib/pleroma/web/endpoint.ex | 2 - .../web/mastodon_api/mastodon_socket.ex | 74 ----------- .../web/mastodon_api/websocket_handler.ex | 120 ++++++++++++++++++ lib/pleroma/web/streamer.ex | 22 ++-- 5 files changed, 141 insertions(+), 86 deletions(-) delete mode 100644 lib/pleroma/web/mastodon_api/mastodon_socket.ex create mode 100644 lib/pleroma/web/mastodon_api/websocket_handler.ex diff --git a/config/config.exs b/config/config.exs index 1777a54c0..df6ea09ae 100644 --- a/config/config.exs +++ b/config/config.exs @@ -50,6 +50,15 @@ # Configures the endpoint config :pleroma, Pleroma.Web.Endpoint, url: [host: "localhost"], + http: [ + dispatch: [ + {:_, + [ + {"/api/v1/streaming", Elixir.Pleroma.Web.MastodonAPI.WebsocketHandler, []}, + {:_, Plug.Adapters.Cowboy.Handler, {Pleroma.Web.Endpoint, []}} + ]} + ] + ], protocol: "https", secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl", signing_salt: "CqaoopA2", diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index aacc88195..e52667c72 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -3,8 +3,6 @@ defmodule Pleroma.Web.Endpoint do socket("/socket", Pleroma.Web.UserSocket) - socket("/api/v1", Pleroma.Web.MastodonAPI.MastodonSocket, websocket: [path: "/streaming"]) - # Serve at "/" the static files from "priv/static" directory. # # You should set gzip to true if you are running phoenix.digest diff --git a/lib/pleroma/web/mastodon_api/mastodon_socket.ex b/lib/pleroma/web/mastodon_api/mastodon_socket.ex deleted file mode 100644 index 1b75897b5..000000000 --- a/lib/pleroma/web/mastodon_api/mastodon_socket.ex +++ /dev/null @@ -1,74 +0,0 @@ -defmodule Pleroma.Web.MastodonAPI.MastodonSocket do - use Phoenix.Socket - - alias Pleroma.Web.OAuth.Token - alias Pleroma.{User, Repo} - - @spec connect(params :: map(), Phoenix.Socket.t()) :: {:ok, Phoenix.Socket.t()} | :error - def connect(%{"access_token" => token} = params, socket) do - with %Token{user_id: user_id} <- Repo.get_by(Token, token: token), - %User{} = user <- Repo.get(User, user_id), - stream - when stream in [ - "public", - "public:local", - "public:media", - "public:local:media", - "user", - "direct", - "list", - "hashtag" - ] <- params["stream"] do - topic = - case stream do - "hashtag" -> "hashtag:#{params["tag"]}" - "list" -> "list:#{params["list"]}" - _ -> stream - end - - socket = - socket - |> assign(:topic, topic) - |> assign(:user, user) - - Pleroma.Web.Streamer.add_socket(topic, socket) - {:ok, socket} - else - _e -> :error - end - end - - def connect(%{"stream" => stream} = params, socket) - when stream in ["public", "public:local", "hashtag"] do - topic = - case stream do - "hashtag" -> "hashtag:#{params["tag"]}" - _ -> stream - end - - socket = - socket - |> assign(:topic, topic) - - Pleroma.Web.Streamer.add_socket(topic, socket) - {:ok, socket} - end - - def connect(_params, _socket), do: :error - - def id(_), do: nil - - def handle(:text, message, _state) do - # | :ok - # | state - # | {:text, message} - # | {:text, message, state} - # | {:close, "Goodbye!"} - {:text, message} - end - - def handle(:closed, _, %{socket: socket}) do - topic = socket.assigns[:topic] - Pleroma.Web.Streamer.remove_socket(topic, socket) - end -end diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex new file mode 100644 index 000000000..11e0e1696 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -0,0 +1,120 @@ +defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do + require Logger + + alias Pleroma.Web.OAuth.Token + alias Pleroma.{User, Repo} + + @behaviour :cowboy_websocket_handler + + @streams [ + "public", + "public:local", + "public:media", + "public:local:media", + "user", + "direct", + "list", + "hashtag" + ] + @anonymous_streams ["public", "public:local", "hashtag"] + + # Handled by periodic keepalive in Pleroma.Web.Streamer. + @timeout :infinity + + def init(_type, _req, _opts) do + {:upgrade, :protocol, :cowboy_websocket} + end + + def websocket_init(_type, req, _opts) do + with {qs, req} <- :cowboy_req.qs(req), + params <- :cow_qs.parse_qs(qs), + access_token <- List.keyfind(params, "access_token", 0), + {_, stream} <- List.keyfind(params, "stream", 0), + {:ok, user} <- allow_request(stream, access_token), + topic when is_binary(topic) <- expand_topic(stream, params) do + send(self(), :subscribe) + {:ok, req, %{user: user, topic: topic}, @timeout} + else + {:error, code} -> + Logger.debug("#{__MODULE__} denied connection: #{inspect(code)} - #{inspect(req)}") + {:ok, req} = :cowboy_req.reply(code, req) + {:shutdown, req} + + error -> + Logger.debug("#{__MODULE__} denied connection: #{inspect(error)} - #{inspect(req)}") + {:shutdown, req} + end + end + + # We never receive messages. + def websocket_handle(_frame, req, state) do + {:ok, req, state} + end + + def websocket_info(:subscribe, req, state) do + Logger.debug( + "#{__MODULE__} accepted websocket connection for user #{ + (state.user || %{id: "anonymous"}).id + }, topic #{state.topic}" + ) + + Pleroma.Web.Streamer.add_socket(state.topic, streamer_socket(state)) + {:ok, req, state} + end + + def websocket_info({:text, message}, req, state) do + {:reply, {:text, message}, req, state} + end + + def websocket_terminate(reason, _req, state) do + Logger.debug( + "#{__MODULE__} terminating websocket connection for user #{ + (state.user || %{id: "anonymous"}).id + }, topic #{state.topic || "?"}: #{inspect(reason)}" + ) + + Pleroma.Web.Streamer.remove_socket(state.topic, streamer_socket(state)) + :ok + end + + # Public streams without authentication. + defp allow_request(stream, nil) when stream in @anonymous_streams do + {:ok, nil} + end + + # Authenticated streams. + defp allow_request(stream, {"access_token", access_token}) when stream in @streams do + with %Token{user_id: user_id} <- Repo.get_by(Token, token: access_token), + user = %User{} <- Repo.get(User, user_id) do + {:ok, user} + else + _ -> {:error, 403} + end + end + + # Not authenticated. + defp allow_request(stream, _) when stream in @streams, do: {:error, 403} + + # No matching stream. + defp allow_request(_, _), do: {:error, 404} + + defp expand_topic("hashtag", params) do + case List.keyfind(params, "tag", 0) do + {_, tag} -> "hashtag:#{tag}" + _ -> nil + end + end + + defp expand_topic("list", params) do + case List.keyfind(params, "list", 0) do + {_, list} -> "list:#{list}" + _ -> nil + end + end + + defp expand_topic(topic, _), do: topic + + defp streamer_socket(state) do + %{transport_pid: self(), assigns: state} + end +end diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index 29c44e9d5..e1eecba4d 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -4,17 +4,9 @@ defmodule Pleroma.Web.Streamer do alias Pleroma.{User, Notification, Activity, Object, Repo} alias Pleroma.Web.ActivityPub.ActivityPub - def init(args) do - {:ok, args} - end + @keepalive_interval :timer.seconds(30) def start_link do - spawn(fn -> - # 30 seconds - Process.sleep(1000 * 30) - GenServer.cast(__MODULE__, %{action: :ping}) - end) - GenServer.start_link(__MODULE__, %{}, name: __MODULE__) end @@ -30,6 +22,16 @@ def stream(topic, item) do GenServer.cast(__MODULE__, %{action: :stream, topic: topic, item: item}) end + def init(args) do + spawn(fn -> + # 30 seconds + Process.sleep(@keepalive_interval) + GenServer.cast(__MODULE__, %{action: :ping}) + end) + + {:ok, args} + end + def handle_cast(%{action: :ping}, topics) do Map.values(topics) |> List.flatten() @@ -40,7 +42,7 @@ def handle_cast(%{action: :ping}, topics) do spawn(fn -> # 30 seconds - Process.sleep(1000 * 30) + Process.sleep(@keepalive_interval) GenServer.cast(__MODULE__, %{action: :ping}) end) From b12a90491156f1b31defd7aa1b322a86867dbf2b Mon Sep 17 00:00:00 2001 From: href Date: Mon, 17 Dec 2018 17:09:06 +0100 Subject: [PATCH 4/5] Integration tests for mastodon websocket --- config/test.exs | 3 +- mix.exs | 3 +- mix.lock | 1 + test/integration/mastodon_websocket_test.exs | 100 ++++++++++++++++++ test/support/websocket_client.ex | 58 ++++++++++ .../web/mastodon_api/mastodon_socket_test.exs | 31 ------ 6 files changed, 163 insertions(+), 33 deletions(-) create mode 100644 test/integration/mastodon_websocket_test.exs create mode 100644 test/support/websocket_client.ex delete mode 100644 test/web/mastodon_api/mastodon_socket_test.exs diff --git a/config/test.exs b/config/test.exs index 5c6acfead..8f4a2dc17 100644 --- a/config/test.exs +++ b/config/test.exs @@ -4,7 +4,8 @@ # you can enable the server option below. config :pleroma, Pleroma.Web.Endpoint, http: [port: 4001], - server: false + url: [port: 4001], + server: true # Print only warnings and errors during test config :logger, level: :warn diff --git a/mix.exs b/mix.exs index 52974c841..0e8a7026c 100644 --- a/mix.exs +++ b/mix.exs @@ -73,7 +73,8 @@ defp deps do {:ex_doc, "> 0.18.3 and < 0.20.0", only: :dev, runtime: false}, {:web_push_encryption, "~> 0.2.1"}, {:swoosh, "~> 0.20"}, - {:gen_smtp, "~> 0.13"} + {:gen_smtp, "~> 0.13"}, + {:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test} ] end diff --git a/mix.lock b/mix.lock index 0a3b6f715..96625a65e 100644 --- a/mix.lock +++ b/mix.lock @@ -59,4 +59,5 @@ "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}, "unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"}, "web_push_encryption": {:hex, :web_push_encryption, "0.2.1", "d42cecf73420d9dc0053ba3299cc8c8d6ff2be2487d67ca2a57265868e4d9a98", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, + "websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []}, } diff --git a/test/integration/mastodon_websocket_test.exs b/test/integration/mastodon_websocket_test.exs new file mode 100644 index 000000000..b5f3d3a47 --- /dev/null +++ b/test/integration/mastodon_websocket_test.exs @@ -0,0 +1,100 @@ +defmodule Pleroma.Integration.MastodonWebsocketTest do + use Pleroma.DataCase + + import Pleroma.Factory + + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.OAuth + alias Pleroma.Integration.WebsocketClient + alias Pleroma.Web.Streamer + + @path Pleroma.Web.Endpoint.url() + |> URI.parse() + |> Map.put(:scheme, "ws") + |> Map.put(:path, "/api/v1/streaming") + |> URI.to_string() + + setup do + GenServer.start(Streamer, %{}, name: Streamer) + + on_exit(fn -> + if pid = Process.whereis(Streamer) do + Process.exit(pid, :kill) + end + end) + end + + def start_socket(qs \\ nil, headers \\ []) do + path = + case qs do + nil -> @path + qs -> @path <> qs + end + + WebsocketClient.start_link(self(), path, headers) + end + + test "refuses invalid requests" do + assert {:error, {400, _}} = start_socket() + assert {:error, {404, _}} = start_socket("?stream=ncjdk") + end + + test "requires authentication and a valid token for protected streams" do + assert {:error, {403, _}} = start_socket("?stream=user&access_token=aaaaaaaaaaaa") + assert {:error, {403, _}} = start_socket("?stream=user") + end + + test "allows public streams without authentication" do + assert {:ok, _} = start_socket("?stream=public") + assert {:ok, _} = start_socket("?stream=public:local") + assert {:ok, _} = start_socket("?stream=hashtag&tag=lain") + end + + test "receives well formatted events" do + user = insert(:user) + {:ok, _} = start_socket("?stream=public") + {:ok, activity} = CommonAPI.post(user, %{"status" => "nice echo chamber"}) + + assert_receive {:text, raw_json}, 1_000 + assert {:ok, json} = Jason.decode(raw_json) + + assert "update" == json["event"] + assert json["payload"] + assert {:ok, json} = Jason.decode(json["payload"]) + + # Note: we remove the "statuses_count" from this result as it changes in the meantime + + view_json = + Pleroma.Web.MastodonAPI.StatusView.render("status.json", activity: activity, for: nil) + |> Jason.encode!() + |> Jason.decode!() + |> put_in(["account", "statuses_count"], 0) + + assert json == view_json + end + + describe "with a valid user token" do + setup do + {:ok, app} = + Pleroma.Repo.insert( + OAuth.App.register_changeset(%OAuth.App{}, %{ + client_name: "client", + scopes: "scope", + redirect_uris: "url" + }) + ) + + user = insert(:user) + + {:ok, auth} = OAuth.Authorization.create_authorization(app, user) + + {:ok, token} = OAuth.Token.exchange_token(app, auth) + + %{user: user, token: token} + end + + test "accepts valid tokens", state do + assert {:ok, _} = start_socket("?stream=user&access_token=#{state.token.token}") + end + end +end diff --git a/test/support/websocket_client.ex b/test/support/websocket_client.ex new file mode 100644 index 000000000..57e9bb17f --- /dev/null +++ b/test/support/websocket_client.ex @@ -0,0 +1,58 @@ +defmodule Pleroma.Integration.WebsocketClient do + # https://github.com/phoenixframework/phoenix/blob/master/test/support/websocket_client.exs + + @doc """ + Starts the WebSocket server for given ws URL. Received Socket.Message's + are forwarded to the sender pid + """ + def start_link(sender, url, headers \\ []) do + :crypto.start() + :ssl.start() + + :websocket_client.start_link( + String.to_charlist(url), + __MODULE__, + [sender], + extra_headers: headers + ) + end + + @doc """ + Closes the socket + """ + def close(socket) do + send(socket, :close) + end + + @doc """ + Sends a low-level text message to the client. + """ + def send_text(server_pid, msg) do + send(server_pid, {:text, msg}) + end + + @doc false + def init([sender], _conn_state) do + {:ok, %{sender: sender}} + end + + @doc false + def websocket_handle(frame, _conn_state, state) do + send(state.sender, frame) + {:ok, state} + end + + @doc false + def websocket_info({:text, msg}, _conn_state, state) do + {:reply, {:text, msg}, state} + end + + def websocket_info(:close, _conn_state, _state) do + {:close, <<>>, "done"} + end + + @doc false + def websocket_terminate(_reason, _conn_state, _state) do + :ok + end +end diff --git a/test/web/mastodon_api/mastodon_socket_test.exs b/test/web/mastodon_api/mastodon_socket_test.exs deleted file mode 100644 index 5d9b96861..000000000 --- a/test/web/mastodon_api/mastodon_socket_test.exs +++ /dev/null @@ -1,31 +0,0 @@ -defmodule Pleroma.Web.MastodonApi.MastodonSocketTest do - use Pleroma.DataCase - - alias Pleroma.Web.{Streamer, CommonAPI} - - import Pleroma.Factory - - test "public is working when non-authenticated" do - user = insert(:user) - - task = - Task.async(fn -> - assert_receive {:text, _}, 4_000 - end) - - fake_socket = %{ - transport_pid: task.pid, - assigns: %{} - } - - topics = %{ - "public" => [fake_socket] - } - - {:ok, activity} = CommonAPI.post(user, %{"status" => "Test"}) - - Streamer.push_to_socket(topics, "public", activity) - - Task.await(task) - end -end From ba9ef77b6ae7fd6ade9eb87a418f9c87c7d94647 Mon Sep 17 00:00:00 2001 From: href Date: Mon, 17 Dec 2018 17:28:03 +0100 Subject: [PATCH 5/5] Switch to phoenixframework/phoenix as the needed change is merged. --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 0e8a7026c..e9705bcf9 100644 --- a/mix.exs +++ b/mix.exs @@ -44,7 +44,7 @@ defp elixirc_paths(_), do: ["lib"] defp deps do [ # Until Phoenix 1.4.1 is released - {:phoenix, github: "feld/phoenix", branch: "v1.4"}, + {:phoenix, github: "phoenixframework/phoenix", branch: "v1.4"}, {:plug_cowboy, "~> 1.0"}, {:phoenix_pubsub, "~> 1.1"}, {:phoenix_ecto, "~> 3.3"}, diff --git a/mix.lock b/mix.lock index 96625a65e..dddb04aad 100644 --- a/mix.lock +++ b/mix.lock @@ -40,7 +40,7 @@ "nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"}, - "phoenix": {:git, "https://github.com/feld/phoenix.git", "38e03a6c18eabe24284d9bf1e1dbc09fd24547ad", [branch: "v1.4"]}, + "phoenix": {:git, "https://github.com/phoenixframework/phoenix.git", "ea22dc50b574178a300ecd19253443960407df93", [branch: "v1.4"]}, "phoenix_ecto": {:hex, :phoenix_ecto, "3.3.0", "702f6e164512853d29f9d20763493f2b3bcfcb44f118af2bc37bb95d0801b480", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_html": {:hex, :phoenix_html, "2.11.2", "86ebd768258ba60a27f5578bec83095bdb93485d646fc4111db8844c316602d6", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.1", "6668d787e602981f24f17a5fbb69cc98f8ab085114ebfac6cc36e10a90c8e93c", [:mix], [], "hexpm"},