Merge branch 'phoenix14' into 'develop'
authorlambda <pleromagit@rogerbraun.net>
Mon, 17 Dec 2018 19:30:05 +0000 (19:30 +0000)
committerlambda <pleromagit@rogerbraun.net>
Mon, 17 Dec 2018 19:30:05 +0000 (19:30 +0000)
Upgrade to Phoenix 1.4

Closes #451

See merge request pleroma/pleroma!560

16 files changed:
config/config.exs
config/test.exs
lib/pleroma/plugs/federating_plug.ex
lib/pleroma/web/channels/user_socket.ex
lib/pleroma/web/endpoint.ex
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
lib/pleroma/web/mastodon_api/mastodon_socket.ex [deleted file]
lib/pleroma/web/mastodon_api/websocket_handler.ex [new file with mode: 0644]
lib/pleroma/web/streamer.ex
lib/pleroma/web/twitter_api/twitter_api_controller.ex
mix.exs
mix.lock
test/integration/mastodon_websocket_test.exs [new file with mode: 0644]
test/support/websocket_client.ex [new file with mode: 0644]
test/web/mastodon_api/mastodon_socket_test.exs [deleted file]
test/web/web_finger/web_finger_controller_test.exs

index 1777a54c0918d0d8b2b1a158adb49160b364e841..df6ea09ae4c894dfa0d85fc5d43c5763a44b6716 100644 (file)
@@ -50,6 +50,15 @@ config :pleroma, :uri_schemes,
 # 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",
index 5c6acfead9b7bf1ed6ba96e0d5b1ef3d3c6a3ec0..8f4a2dc17bf5ab5f7e35012c0851db17167f5839 100644 (file)
@@ -4,7 +4,8 @@ use Mix.Config
 # 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
index f0442ca15e9c5543453d4b43387902de3d2cad3e..b5326d97b67108984653ba340e47139b26bb27db 100644 (file)
@@ -11,7 +11,8 @@ defmodule Pleroma.Web.FederatingPlug 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
index 07ddee16913646fe219ada404bb635da98ef56a3..9918d3b49af9f94d8f52e415b3b21209684b86f9 100644 (file)
@@ -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
index c5f9d51d92d77cc9675d384b842810c98d838a19..e52667c72691245aabb10e1b06ae9788526478dc 100644 (file)
@@ -3,8 +3,6 @@ defmodule Pleroma.Web.Endpoint do
 
   socket("/socket", Pleroma.Web.UserSocket)
 
-  socket("/api/v1", Pleroma.Web.MastodonAPI.MastodonSocket)
-
   # Serve at "/" the static files from "priv/static" directory.
   #
   # You should set gzip to true if you are running phoenix.digest
index 726807f0aa3590cef98329fa7b3807dcc1a1536f..665b7543769ac5f59b22d1414f081c7ebe219d74 100644 (file)
@@ -226,7 +226,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
              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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
              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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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})
     else
       _e ->
         conn
@@ -968,7 +1024,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     end
   end
 
-  def try_render(conn, _, _, _) do
+  def try_render(conn, _, _) do
     conn
     |> put_status(501)
     |> json(%{error: "Can't display this activity"})
diff --git a/lib/pleroma/web/mastodon_api/mastodon_socket.ex b/lib/pleroma/web/mastodon_api/mastodon_socket.ex
deleted file mode 100644 (file)
index 755ac57..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-defmodule Pleroma.Web.MastodonAPI.MastodonSocket do
-  use Phoenix.Socket
-
-  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),
-         %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 (file)
index 0000000..11e0e16
--- /dev/null
@@ -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
index 29c44e9d5e5f021357af71a3b2ecc390288fdeca..e1eecba4d297547347b7eb0ec1bafc162792cc27 100644 (file)
@@ -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 @@ defmodule Pleroma.Web.Streamer 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 @@ defmodule Pleroma.Web.Streamer do
 
     spawn(fn ->
       # 30 seconds
-      Process.sleep(1000 * 30)
+      Process.sleep(@keepalive_interval)
       GenServer.cast(__MODULE__, %{action: :ping})
     end)
 
index 38eff8191785da354ed9b5dedeb8d83092855cb6..327620302a6f9081478e2cc40aff2998e90176db 100644 (file)
@@ -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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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, _) do
@@ -163,7 +179,9 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 5b56b94904dd8c1551a2bab4757f6f5c619e65a2..e9705bcf90c1877ca3d6e78bc08e8d44340d6b68 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -43,12 +43,13 @@ defmodule Pleroma.Mixfile do
   # 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: "phoenixframework/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"},
@@ -72,7 +73,8 @@ defmodule Pleroma.Mixfile 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
 
index 6f8ca5bb034be2cafc2fc4d8bab5b9fe99d394d6..dddb04aadac97f312a6e097529a5bff31db767c9 100644 (file)
--- 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"},
   "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/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.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"},
@@ -57,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 (file)
index 0000000..b5f3d3a
--- /dev/null
@@ -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 (file)
index 0000000..57e9bb1
--- /dev/null
@@ -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 (file)
index 5d9b968..0000000
+++ /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
index 3bc878532f0367f41b610d4a7375620f2c7ed615..844ff51d2122412129d765b82512f185e0d01770 100644 (file)
@@ -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