From 8fb73c28bbeccb6a7462e4a0e9fb58726b68bcf5 Mon Sep 17 00:00:00 2001
From: Roger Braun <roger@rogerbraun.net>
Date: Sat, 22 Apr 2017 13:44:21 +0200
Subject: [PATCH] Only have one subscription per callback.

---
 config/config.exs                           |  2 +
 config/test.exs                             |  2 +
 lib/pleroma/web/websub/websub.ex            | 54 ++++++++++++++++++++-
 lib/pleroma/web/websub/websub_controller.ex | 41 ++--------------
 test/web/websub/websub_controller_test.exs  |  8 ---
 test/web/websub/websub_test.exs             | 48 +++++++++++++++++-
 6 files changed, 107 insertions(+), 48 deletions(-)

diff --git a/config/config.exs b/config/config.exs
index 18a2490a4..3826dddff 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -30,6 +30,8 @@
   "application/xrd+xml" => ["xrd+xml"]
 }
 
+config :pleroma, :websub_verifier, Pleroma.Web.Websub
+
 # Import environment specific config. This must remain at the bottom
 # of this file so it overrides the configuration defined above.
 import_config "#{Mix.env}.exs"
diff --git a/config/test.exs b/config/test.exs
index f5d6f240d..5d91279a2 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -24,3 +24,5 @@
 
 # Reduce hash rounds for testing
 config :comeonin, :pbkdf2_rounds, 1
+
+config :pleroma, :websub_verifier, Pleroma.Web.WebsubMock
diff --git a/lib/pleroma/web/websub/websub.ex b/lib/pleroma/web/websub/websub.ex
index 26a10788a..50878e3c4 100644
--- a/lib/pleroma/web/websub/websub.ex
+++ b/lib/pleroma/web/websub/websub.ex
@@ -3,12 +3,15 @@ defmodule Pleroma.Web.Websub do
   alias Pleroma.Websub
   alias Pleroma.Web.Websub.WebsubServerSubscription
   alias Pleroma.Web.OStatus.FeedRepresenter
+  alias Pleroma.Web.OStatus
 
   import Ecto.Query
 
+  @websub_verifier Application.get_env(:pleroma, :websub_verifier)
+
   def verify(subscription, getter \\ &HTTPoison.get/3 ) do
     challenge = Base.encode16(:crypto.strong_rand_bytes(8))
-    lease_seconds = NaiveDateTime.diff(subscription.valid_until, subscription.inserted_at) |> to_string
+    lease_seconds = NaiveDateTime.diff(subscription.valid_until, subscription.updated_at) |> to_string
 
     params = %{
       "hub.challenge": challenge,
@@ -48,4 +51,53 @@ def publish(topic, user, activity) do
           ])
     end)
   end
+
+  def incoming_subscription_request(user, params) do
+    with {:ok, topic} <- valid_topic(params, user),
+         {:ok, lease_time} <- lease_time(params),
+         secret <- params["hub.secret"],
+         callback <- params["hub.callback"]
+    do
+      subscription = get_subscription(topic, callback)
+      data = %{
+        state: subscription.state || "requested",
+        topic: topic,
+        secret: secret,
+        callback: callback
+      }
+
+      change = Ecto.Changeset.change(subscription, data)
+      websub = Repo.insert_or_update!(change)
+
+      change = Ecto.Changeset.change(websub, %{valid_until: NaiveDateTime.add(websub.updated_at, lease_time)})
+      websub = Repo.update!(change)
+
+      # Just spawn that for now, maybe pool later.
+      spawn(fn -> @websub_verifier.verify(websub) end)
+
+      {:ok, websub}
+    else {:error, reason} ->
+      {:error, reason}
+    end
+  end
+
+  defp get_subscription(topic, callback) do
+    Repo.get_by(WebsubServerSubscription, topic: topic, callback: callback) || %WebsubServerSubscription{}
+  end
+
+  defp lease_time(%{"hub.lease_seconds" => lease_seconds}) do
+    {:ok, String.to_integer(lease_seconds)}
+  end
+
+  defp lease_time(_) do
+    {:ok, 60 * 60 * 24 * 3} # three days
+  end
+
+  defp valid_topic(%{"hub.topic" => topic}, user) do
+    if topic == OStatus.feed_path(user) do
+      {:ok, topic}
+    else
+      {:error, "Wrong topic requested, expected #{OStatus.feed_path(user)}, got #{topic}"}
+    end
+  end
 end
diff --git a/lib/pleroma/web/websub/websub_controller.ex b/lib/pleroma/web/websub/websub_controller.ex
index 5766dff64..5d54c6ef5 100644
--- a/lib/pleroma/web/websub/websub_controller.ex
+++ b/lib/pleroma/web/websub/websub_controller.ex
@@ -1,32 +1,13 @@
 defmodule Pleroma.Web.Websub.WebsubController do
   use Pleroma.Web, :controller
-  alias Pleroma.Web.Websub.WebsubServerSubscription
-  alias Pleroma.{Repo, User}
-  alias Pleroma.Web.OStatus
+  alias Pleroma.User
   alias Pleroma.Web.Websub
+
   def websub_subscription_request(conn, %{"nickname" => nickname} = params) do
     user = User.get_cached_by_nickname(nickname)
 
-    with {:ok, topic} <- valid_topic(params, user),
-         {:ok, lease_time} <- lease_time(params),
-         secret <- params["hub.secret"]
+    with {:ok, _websub} <- Websub.incoming_subscription_request(user, params)
     do
-      data = %{
-        state: "requested",
-        topic: topic,
-        secret: secret,
-        callback: params["hub.callback"]
-      }
-
-      change = Ecto.Changeset.change(%WebsubServerSubscription{}, data)
-      websub = Repo.insert!(change)
-
-      change = Ecto.Changeset.change(websub, %{valid_until: NaiveDateTime.add(websub.inserted_at, lease_time)})
-      websub = Repo.update!(change)
-
-      # Just spawn that for now, maybe pool later.
-      spawn(fn -> Websub.verify(websub) end)
-
       conn
       |> send_resp(202, "Accepted")
     else {:error, reason} ->
@@ -34,20 +15,4 @@ def websub_subscription_request(conn, %{"nickname" => nickname} = params) do
       |> send_resp(500, reason)
     end
   end
-
-  defp lease_time(%{"hub.lease_seconds" => lease_seconds}) do
-    {:ok, String.to_integer(lease_seconds)}
-  end
-
-  defp lease_time(_) do
-    {:ok, 60 * 60 * 24 * 3} # three days
-  end
-
-  defp valid_topic(%{"hub.topic" => topic}, user) do
-    if topic == OStatus.feed_path(user) do
-      {:ok, topic}
-    else
-      {:error, "Wrong topic requested, expected #{OStatus.feed_path(user)}, got #{topic}"}
-    end
-  end
 end
diff --git a/test/web/websub/websub_controller_test.exs b/test/web/websub/websub_controller_test.exs
index 584db0a19..9a0a5c61b 100644
--- a/test/web/websub/websub_controller_test.exs
+++ b/test/web/websub/websub_controller_test.exs
@@ -1,8 +1,6 @@
 defmodule Pleroma.Web.Websub.WebsubControllerTest do
   use Pleroma.Web.ConnCase
   import Pleroma.Factory
-  alias Pleroma.Repo
-  alias Pleroma.Web.Websub.WebsubServerSubscription
 
   test "websub subscription request", %{conn: conn} do
     user = insert(:user)
@@ -21,11 +19,5 @@ test "websub subscription request", %{conn: conn} do
     |> post(path, data)
 
     assert response(conn, 202) == "Accepted"
-    subscription = Repo.one!(WebsubServerSubscription)
-    assert subscription.topic == Pleroma.Web.OStatus.feed_path(user)
-    assert subscription.state == "requested"
-    assert subscription.secret == "a random secret"
-    assert subscription.callback == "http://example.org/sub"
-    assert subscription.valid_until == NaiveDateTime.add(subscription.inserted_at, 100)
   end
 end
diff --git a/test/web/websub/websub_test.exs b/test/web/websub/websub_test.exs
index 36ea82299..5fe91d0f8 100644
--- a/test/web/websub/websub_test.exs
+++ b/test/web/websub/websub_test.exs
@@ -1,6 +1,12 @@
+defmodule Pleroma.Web.WebsubMock do
+  def verify(sub) do
+    {:ok, sub}
+  end
+end
 defmodule Pleroma.Web.WebsubTest do
   use Pleroma.DataCase
   alias Pleroma.Web.Websub
+  alias Pleroma.Web.Websub.WebsubServerSubscription
   import Pleroma.Factory
 
   test "a verification of a request that is accepted" do
@@ -29,7 +35,6 @@ test "a verification of a request that is accepted" do
 
   test "a verification of a request that doesn't return 200" do
     sub = insert(:websub_subscription)
-    topic = sub.topic
 
     getter = fn (_path, _headers, _options) ->
       {:ok, %HTTPoison.Response{
@@ -41,4 +46,45 @@ test "a verification of a request that doesn't return 200" do
     {:error, sub} = Websub.verify(sub, getter)
     assert sub.state == "rejected"
   end
+
+  test "an incoming subscription request" do
+    user = insert(:user)
+
+    data = %{
+      "hub.callback" => "http://example.org/sub",
+      "hub.mode" => "subscription",
+      "hub.topic" => Pleroma.Web.OStatus.feed_path(user),
+      "hub.secret" => "a random secret",
+      "hub.lease_seconds" => "100"
+    }
+
+
+    {:ok, subscription } = Websub.incoming_subscription_request(user, data)
+    assert subscription.topic == Pleroma.Web.OStatus.feed_path(user)
+    assert subscription.state == "requested"
+    assert subscription.secret == "a random secret"
+    assert subscription.callback == "http://example.org/sub"
+  end
+
+  test "an incoming subscription request for an existing subscription" do
+    user = insert(:user)
+    sub = insert(:websub_subscription, state: "accepted", topic: Pleroma.Web.OStatus.feed_path(user))
+
+    data = %{
+      "hub.callback" => sub.callback,
+      "hub.mode" => "subscription",
+      "hub.topic" => Pleroma.Web.OStatus.feed_path(user),
+      "hub.secret" => "a random secret",
+      "hub.lease_seconds" => "100"
+    }
+
+
+    {:ok, subscription } = Websub.incoming_subscription_request(user, data)
+    assert subscription.topic == Pleroma.Web.OStatus.feed_path(user)
+    assert subscription.state == sub.state
+    assert subscription.secret == "a random secret"
+    assert subscription.callback == sub.callback
+    assert length(Repo.all(WebsubServerSubscription)) == 1
+    assert subscription.id == sub.id
+  end
 end