diff --git a/config/config.exs b/config/config.exs index 3826dddff..a5df31b5a 100644 --- a/config/config.exs +++ b/config/config.exs @@ -30,7 +30,8 @@ config :mime, :types, %{ "application/xrd+xml" => ["xrd+xml"] } -config :pleroma, :websub_verifier, Pleroma.Web.Websub +config :pleroma, :websub, Pleroma.Web.Websub +config :pleroma, :ostatus, Pleroma.Web.OStatus # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. diff --git a/config/test.exs b/config/test.exs index 5d91279a2..85b6ad26b 100644 --- a/config/test.exs +++ b/config/test.exs @@ -25,4 +25,5 @@ config :pleroma, Pleroma.Repo, # Reduce hash rounds for testing config :comeonin, :pbkdf2_rounds, 1 -config :pleroma, :websub_verifier, Pleroma.Web.WebsubMock +config :pleroma, :websub, Pleroma.Web.WebsubMock +config :pleroma, :ostatus, Pleroma.Web.OStatusMock diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index f489ed837..38df13540 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -2,7 +2,7 @@ defmodule Pleroma.Web.Federator do alias Pleroma.User require Logger - @websub_verifier Application.get_env(:pleroma, :websub_verifier) + @websub Application.get_env(:pleroma, :websub) def handle(:publish, activity) do Logger.debug("Running publish for #{activity.data["id"]}") @@ -13,7 +13,7 @@ defmodule Pleroma.Web.Federator do def handle(:verify_websub, websub) do Logger.debug("Running websub verification for #{websub.id} (#{websub.topic}, #{websub.callback})") - @websub_verifier.verify(websub) + @websub.verify(websub) end def handle(type, payload) do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index bff981f9f..2ff75ec5d 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -75,8 +75,9 @@ defmodule Pleroma.Web.Router do get "/users/:nickname/feed", OStatus.OStatusController, :feed post "/users/:nickname/salmon", OStatus.OStatusController, :salmon_incoming - post "/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation post "/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request + get "/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation + post "/push/subscriptions/:id", Websub.WebsubController, :websub_incoming end scope "/.well-known", Pleroma.Web do diff --git a/lib/pleroma/web/websub/websub.ex b/lib/pleroma/web/websub/websub.ex index ad352ee26..ad9e47b46 100644 --- a/lib/pleroma/web/websub/websub.ex +++ b/lib/pleroma/web/websub/websub.ex @@ -42,8 +42,7 @@ defmodule Pleroma.Web.Websub do response = FeedRepresenter.to_simple_form(user, [activity], [user]) |> :xmerl.export_simple(:xmerl_xml) - signature = :crypto.hmac(:sha, sub.secret, response) |> Base.encode16 - + signature = sign(sub.secret, response) HTTPoison.post(sub.callback, response, [ {"Content-Type", "application/atom+xml"}, {"X-Hub-Signature", "sha1=#{signature}"} @@ -51,6 +50,10 @@ defmodule Pleroma.Web.Websub do end) end + def sign(secret, doc) do + :crypto.hmac(:sha, secret, doc) |> Base.encode16 + end + def incoming_subscription_request(user, %{"hub.mode" => "subscribe"} = params) do with {:ok, topic} <- valid_topic(params, user), {:ok, lease_time} <- lease_time(params), diff --git a/lib/pleroma/web/websub/websub_controller.ex b/lib/pleroma/web/websub/websub_controller.ex index c6b15c0c2..cd59a70a3 100644 --- a/lib/pleroma/web/websub/websub_controller.ex +++ b/lib/pleroma/web/websub/websub_controller.ex @@ -1,7 +1,11 @@ defmodule Pleroma.Web.Websub.WebsubController do use Pleroma.Web, :controller - alias Pleroma.User + alias Pleroma.{Repo, User} alias Pleroma.Web.Websub + alias Pleroma.Web.Websub.WebsubClientSubscription + require Logger + + @ostatus Application.get_env(:pleroma, :ostatus) def websub_subscription_request(conn, %{"nickname" => nickname} = params) do user = User.get_cached_by_nickname(nickname) @@ -16,8 +20,30 @@ defmodule Pleroma.Web.Websub.WebsubController do end end - def websub_subscription_confirmation(conn, params) do - IO.inspect(params) - conn + def websub_subscription_confirmation(conn, %{"id" => id, "hub.mode" => "subscribe", "hub.challenge" => challenge, "hub.topic" => topic}) do + with %WebsubClientSubscription{} = websub <- Repo.get_by(WebsubClientSubscription, id: id, topic: topic) do + change = Ecto.Changeset.change(websub, %{state: "accepted"}) + {:ok, _websub} = Repo.update(change) + conn + |> send_resp(200, challenge) + else _e -> + conn + |> send_resp(500, "Error") + end + end + + def websub_incoming(conn, %{"id" => id}) do + with "sha1=" <> signature <- hd(get_req_header(conn, "x-hub-signature")), + %WebsubClientSubscription{} = websub <- Repo.get(WebsubClientSubscription, id), + {:ok, body, _conn} = read_body(conn), + ^signature <- Websub.sign(websub.secret, body) do + @ostatus.handle_incoming(body) + conn + |> send_resp(200, "OK") + else _e -> + Logger.debug("Can't handle incoming subscription post") + conn + |> send_resp(500, "Error") + end end end diff --git a/test/web/websub/websub_controller_test.exs b/test/web/websub/websub_controller_test.exs index 8368cafea..521bbb9aa 100644 --- a/test/web/websub/websub_controller_test.exs +++ b/test/web/websub/websub_controller_test.exs @@ -1,6 +1,9 @@ defmodule Pleroma.Web.Websub.WebsubControllerTest do use Pleroma.Web.ConnCase import Pleroma.Factory + alias Pleroma.Web.Websub.WebsubClientSubscription + alias Pleroma.{Repo, Activity} + alias Pleroma.Web.Websub test "websub subscription request", %{conn: conn} do user = insert(:user) @@ -20,4 +23,60 @@ defmodule Pleroma.Web.Websub.WebsubControllerTest do assert response(conn, 202) == "Accepted" end + + test "websub subscription confirmation", %{conn: conn} do + websub = insert(:websub_client_subscription) + + params = %{ + "hub.mode" => "subscribe", + "hub.topic" => websub.topic, + "hub.challenge" => "some challenge", + "hub.lease_seconds" => 100 + } + + conn = conn + |> get("/push/subscriptions/#{websub.id}", params) + + websub = Repo.get(WebsubClientSubscription, websub.id) + + assert response(conn, 200) == "some challenge" + assert websub.state == "accepted" + end + + test "handles incoming feed updates", %{conn: conn} do + websub = insert(:websub_client_subscription) + doc = "some stuff" + signature = Websub.sign(websub.secret, doc) + + conn = conn + |> put_req_header("x-hub-signature", "sha1=" <> signature) + |> put_req_header("content-type", "application/atom+xml") + |> post("/push/subscriptions/#{websub.id}", doc) + + assert response(conn, 200) == "OK" + + assert length(Repo.all(Activity)) == 1 + end + + test "rejects incoming feed updates with the wrong signature", %{conn: conn} do + websub = insert(:websub_client_subscription) + doc = "some stuff" + signature = Websub.sign("wrong secret", doc) + + conn = conn + |> put_req_header("x-hub-signature", "sha1=" <> signature) + |> put_req_header("content-type", "application/atom+xml") + |> post("/push/subscriptions/#{websub.id}", doc) + + assert response(conn, 500) == "Error" + + assert length(Repo.all(Activity)) == 0 + end +end + +defmodule Pleroma.Web.OStatusMock do + import Pleroma.Factory + def handle_incoming(_doc) do + insert(:note_activity) + end end