Make outgoing salmons work.
authorRoger Braun <roger@rogerbraun.net>
Mon, 1 May 2017 11:14:58 +0000 (13:14 +0200)
committerRoger Braun <roger@rogerbraun.net>
Mon, 1 May 2017 11:14:58 +0000 (13:14 +0200)
TODO.txt
lib/pleroma/user.ex
lib/pleroma/web/federator/federator.ex
lib/pleroma/web/ostatus/activity_representer.ex
lib/pleroma/web/salmon/salmon.ex
test/user_test.exs
test/web/ostatus/activity_representer_test.exs
test/web/salmon/salmon_test.exs

index dd85c52392f0b001ff946b65a2e101579aad6410..304e95e779afde9f05948e35ba8a1ba572019d31 100644 (file)
--- a/TODO.txt
+++ b/TODO.txt
@@ -1,5 +1,9 @@
-- Add cache for user fetching / representing. (mostly in TwitterAPI.activity_to_status)
-
 Unliking:
 
 - Add a proper undo activity, find out how to ignore those in twitter api.
+
+WEBSUB:
+
+- Add unsubscription
+- Add periodical renewal
+
index c264d7e90994560824d88c7557fc43d7bbbe3eef..01cbfe796379d864b997796a3879e731095ed4c9 100644 (file)
@@ -121,7 +121,7 @@ defmodule Pleroma.User do
 
   def get_cached_by_nickname(nickname) do
     key = "nickname:#{nickname}"
-    Cachex.get!(:user_cache, key, fallback: fn(_) -> Repo.get_by(User, nickname: nickname) end)
+    Cachex.get!(:user_cache, key, fallback: fn(_) -> get_or_fetch_by_nickname(nickname) end)
   end
 
   def get_by_nickname(nickname) do
@@ -137,7 +137,8 @@ defmodule Pleroma.User do
     with %User{} = user <- get_by_nickname(nickname)  do
       user
     else _e ->
-      with {:ok, user} <- OStatus.make_user(nickname) do
+      with [nick, domain] <- String.split(nickname, "@"),
+           {:ok, user} <- OStatus.make_user(nickname) do
         user
       else _e -> nil
       end
index 38df13540e11788ccfbb338332e2465df441c6a4..5293507b56210a52f5958b89e9ad02143bfedad7 100644 (file)
@@ -7,7 +7,11 @@ defmodule Pleroma.Web.Federator do
   def handle(:publish, activity) do
     Logger.debug("Running publish for #{activity.data["id"]}")
     with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do
+      Logger.debug("Sending #{activity.data["id"]} out via websub")
       Pleroma.Web.Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
+
+      Logger.debug("Sending #{activity.data["id"]} out via salmon")
+      Pleroma.Web.Salmon.publish(actor, activity)
     end
   end
 
index 274111ac9fe31d1c559f3dce934a26f48f7b2666..c64bb3a3bbcdf65852da48ab0b25a1e4afebfd2e 100644 (file)
@@ -1,5 +1,6 @@
 defmodule Pleroma.Web.OStatus.ActivityRepresenter do
   alias Pleroma.Activity
+  alias Pleroma.Web.OStatus.UserRepresenter
   require Logger
 
   defp get_in_reply_to(%{"object" => %{ "inReplyTo" => in_reply_to}}) do
@@ -8,7 +9,17 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
 
   defp get_in_reply_to(_), do: []
 
-  def to_simple_form(%{data: %{"object" => %{"type" => "Note"}}} = activity, user) do
+  defp get_mentions(to) do
+    Enum.map(to, fn
+      ("https://www.w3.org/ns/activitystreams#Public") ->
+        {:link, [rel: "mentioned", "ostatus:object-type": "http://activitystrea.ms/schema/1.0/collection", href: "http://activityschema.org/collection/public"], []}
+      (id) ->
+        {:link, [rel: "mentioned", "ostatus:object-type": "http://activitystrea.ms/schema/1.0/person", href: id], []}
+    end)
+  end
+
+  def to_simple_form(activity, user, with_author \\ false)
+  def to_simple_form(%{data: %{"object" => %{"type" => "Note"}}} = activity, user, with_author) do
     h = fn(str) -> [to_charlist(str)] end
 
     updated_at = activity.updated_at
@@ -22,6 +33,8 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
     end)
 
     in_reply_to = get_in_reply_to(activity.data)
+    author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
+    mentions = activity.data["to"] |> get_mentions
 
     [
       {:"activity:object-type", ['http://activitystrea.ms/schema/1.0/note']},
@@ -33,8 +46,20 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
       {:updated, h.(updated_at)},
       {:"ostatus:conversation", [], h.(activity.data["context"])},
       {:link, [href: h.(activity.data["context"]), rel: 'ostatus:conversation'], []}
-    ] ++ attachments ++ in_reply_to
+    ] ++ attachments ++ in_reply_to ++ author ++ mentions
+  end
+
+  def wrap_with_entry(simple_form) do
+    [{
+      :entry, [
+        xmlns: 'http://www.w3.org/2005/Atom',
+        "xmlns:thr": 'http://purl.org/syndication/thread/1.0',
+        "xmlns:activity": 'http://activitystrea.ms/spec/1.0/',
+        "xmlns:poco": 'http://portablecontacts.net/spec/1.0',
+        "xmlns:ostatus": 'http://ostatus.org/schema/1.0'
+      ], simple_form
+    }]
   end
 
-  def to_simple_form(_,_), do: nil
+  def to_simple_form(_,_,_), do: nil
 end
index 777898cfa23bfe04227bda67997ca9db5ce7b45c..b4f214d4600783947ea7a0aaa86b9853e7037d54 100644 (file)
@@ -1,6 +1,9 @@
 defmodule Pleroma.Web.Salmon do
   use Bitwise
   alias Pleroma.Web.XML
+  alias Pleroma.Web.OStatus.ActivityRepresenter
+  alias Pleroma.User
+  require Logger
 
   def decode(salmon) do
     doc = XML.parse_document(salmon)
@@ -118,4 +121,37 @@ defmodule Pleroma.Web.Salmon do
 
     {:ok, salmon}
   end
+
+  def remote_users(%{data: %{"to" => to}}) do
+    to
+    |> Enum.map(fn(id) -> User.get_cached_by_ap_id(id) end)
+    |> Enum.filter(fn(user) -> user && !user.local end)
+  end
+
+  defp send_to_user(%{info: %{"salmon" => salmon}}, feed, poster) do
+    poster.(salmon, feed, [{"Content-Type", "application/magic-envelope+xml"}])
+  end
+
+  defp send_to_user(_,_,_), do: nil
+
+  def publish(user, activity, poster \\ &HTTPoison.post/3)
+  def publish(%{info: %{"keys" => keys}} = user, activity, poster) do
+    feed = ActivityRepresenter.to_simple_form(activity, user, true)
+    |> ActivityRepresenter.wrap_with_entry
+    |> :xmerl.export_simple(:xmerl_xml)
+    |> to_string
+
+    if feed do
+      {:ok, private, _} = keys_from_pem(keys)
+      {:ok, feed} = encode(private, feed)
+
+      remote_users(activity)
+      |> Enum.each(fn(remote_user) ->
+        Logger.debug("sending salmon to #{remote_user.ap_id}")
+        send_to_user(remote_user, feed, poster)
+      end)
+    end
+  end
+
+  def publish(%{id: id}, _, _), do: Logger.debug("Keys missing for user #{id}")
 end
index 6684aa434ac3516488dd59b3b1870b27f9648033..1331ac97177ee1fee6ac0340e08888ba397f57f6 100644 (file)
@@ -95,6 +95,7 @@ defmodule Pleroma.UserTest do
       assert user == fetched_user
     end
 
+    # TODO: Make the test local.
     test "fetches an external user via ostatus if no user exists" do
       fetched_user = User.get_or_fetch_by_nickname("shp@social.heldscal.la")
       assert fetched_user.nickname == "shp@social.heldscal.la"
@@ -104,6 +105,11 @@ defmodule Pleroma.UserTest do
       fetched_user = User.get_or_fetch_by_nickname("nonexistant@social.heldscal.la")
       assert fetched_user == nil
     end
+
+    test "returns nil for nonexistant local user" do
+      fetched_user = User.get_or_fetch_by_nickname("nonexistant")
+      assert fetched_user == nil
+    end
   end
 end
 
index 6344889b1a82e14fab46018e5cb3bfe5f53d2d2b..439c733d7e33e17a0f0978bfa9001cbf08b4ca58 100644 (file)
@@ -25,6 +25,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest do
     <updated>#{updated_at}</updated>
     <ostatus:conversation>#{note_activity.data["context"]}</ostatus:conversation>
     <link href="#{note_activity.data["context"]}" rel="ostatus:conversation" />
+    <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
     """
 
     tuple = ActivityRepresenter.to_simple_form(note_activity, user)
@@ -61,6 +62,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest do
     <ostatus:conversation>#{answer.data["context"]}</ostatus:conversation>
     <link href="#{answer.data["context"]}" rel="ostatus:conversation" />
     <thr:in-reply-to ref="#{note.data["object"]["id"]}" />
+    <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
     """
 
     tuple = ActivityRepresenter.to_simple_form(answer, user)
index aa77659d0f34ea03bad66671dab9281852c280c9..77dacc1c09ed05a6a7db5f95bc6e3b749d02753f 100644 (file)
@@ -1,6 +1,8 @@
 defmodule Pleroma.Web.Salmon.SalmonTest do
   use Pleroma.DataCase
   alias Pleroma.Web.Salmon
+  alias Pleroma.{Repo, Activity, User}
+  import Pleroma.Factory
 
   @magickey "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwQhh-1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB"
 
@@ -57,4 +59,34 @@ defmodule Pleroma.Web.Salmon.SalmonTest do
 
     assert key == "RSA.uzg6r1peZU0vXGADWxGJ0PE34WvmhjUmydbX5YYdOiXfODVLwCMi1umGoqUDm-mRu4vNEdFBVJU1CpFA7dKzWgIsqsa501i2XqElmEveXRLvNRWFB6nG03Q5OUY2as8eE54BJm0p20GkMfIJGwP6TSFb-ICp3QjzbatuSPJ6xCE=.AQAB"
   end
+
+  test "it pushes an activity to remote accounts it's addressed to" do
+    user_data = %{
+      info: %{
+        "salmon" => "http://example.org/salmon"
+      },
+      local: false
+    }
+
+    mentioned_user = insert(:user, user_data)
+    note = insert(:note)
+    activity_data = %{
+      "id" => Pleroma.Web.ActivityPub.ActivityPub.generate_activity_id,
+      "type" => "Create",
+      "actor" => note.data["actor"],
+      "to" => note.data["to"] ++ [mentioned_user.ap_id],
+      "object" => note.data,
+      "published_at" => DateTime.utc_now() |> DateTime.to_iso8601,
+      "context" => note.data["context"]
+    }
+
+    {:ok, activity} = Repo.insert(%Activity{data: activity_data})
+    user = Repo.get_by(User, ap_id: activity.data["actor"])
+    {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+
+    poster = fn (url, data, headers) ->
+      assert url == "http://example.org/salmon"
+    end
+    Salmon.publish(user, activity, poster)
+  end
 end