From 6843755834192c671aebece505a1ab9322e57eee Mon Sep 17 00:00:00 2001 From: Roger Braun Date: Mon, 1 May 2017 13:14:58 +0200 Subject: [PATCH] Make outgoing salmons work. --- TODO.txt | 8 +++-- lib/pleroma/user.ex | 5 +-- lib/pleroma/web/federator/federator.ex | 4 +++ .../web/ostatus/activity_representer.ex | 31 ++++++++++++++-- lib/pleroma/web/salmon/salmon.ex | 36 +++++++++++++++++++ test/user_test.exs | 6 ++++ .../web/ostatus/activity_representer_test.exs | 2 ++ test/web/salmon/salmon_test.exs | 32 +++++++++++++++++ 8 files changed, 117 insertions(+), 7 deletions(-) diff --git a/TODO.txt b/TODO.txt index dd85c5239..304e95e77 100644 --- 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 + diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index c264d7e90..01cbfe796 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -121,7 +121,7 @@ def get_cached_by_ap_id(ap_id) 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 @@ def get_or_fetch_by_nickname(nickname) 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 diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index 38df13540..5293507b5 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -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 diff --git a/lib/pleroma/web/ostatus/activity_representer.ex b/lib/pleroma/web/ostatus/activity_representer.ex index 274111ac9..c64bb3a3b 100644 --- a/lib/pleroma/web/ostatus/activity_representer.ex +++ b/lib/pleroma/web/ostatus/activity_representer.ex @@ -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 @@ defp get_in_reply_to(%{"object" => %{ "inReplyTo" => in_reply_to}}) 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 @@ def to_simple_form(%{data: %{"object" => %{"type" => "Note"}}} = activity, user) 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 @@ def to_simple_form(%{data: %{"object" => %{"type" => "Note"}}} = activity, user) {: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 to_simple_form(_,_), do: nil + 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 end diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex index 777898cfa..b4f214d46 100644 --- a/lib/pleroma/web/salmon/salmon.ex +++ b/lib/pleroma/web/salmon/salmon.ex @@ -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 @@ def encode(private_key, doc) 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 diff --git a/test/user_test.exs b/test/user_test.exs index 6684aa434..1331ac971 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -95,6 +95,7 @@ test "gets an existing user" 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 @@ test "returns nil if no user could be fetched" 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 diff --git a/test/web/ostatus/activity_representer_test.exs b/test/web/ostatus/activity_representer_test.exs index 6344889b1..439c733d7 100644 --- a/test/web/ostatus/activity_representer_test.exs +++ b/test/web/ostatus/activity_representer_test.exs @@ -25,6 +25,7 @@ test "a note activity" do #{updated_at} #{note_activity.data["context"]} + """ tuple = ActivityRepresenter.to_simple_form(note_activity, user) @@ -61,6 +62,7 @@ test "a reply note" do #{answer.data["context"]} + """ tuple = ActivityRepresenter.to_simple_form(answer, user) diff --git a/test/web/salmon/salmon_test.exs b/test/web/salmon/salmon_test.exs index aa77659d0..77dacc1c0 100644 --- a/test/web/salmon/salmon_test.exs +++ b/test/web/salmon/salmon_test.exs @@ -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 @@ test "it gets a magic key" 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