Page MenuHomePhorge

No OneTemporary

Size
19 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex
index 3e239179e..59c5d8e9e 100644
--- a/lib/pleroma/web/ostatus/ostatus.ex
+++ b/lib/pleroma/web/ostatus/ostatus.ex
@@ -1,136 +1,138 @@
defmodule Pleroma.Web.OStatus do
import Ecto.Query
import Pleroma.Web.XML
require Logger
alias Pleroma.{Repo, User, Web}
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.{WebFinger, Websub}
def feed_path(user) do
"#{user.ap_id}/feed.atom"
end
def pubsub_path(user) do
"#{Web.base_url}/push/hub/#{user.nickname}"
end
def salmon_path(user) do
"#{user.ap_id}/salmon"
end
def handle_incoming(xml_string) do
doc = parse_document(xml_string)
{:xmlObj, :string, object_type } = :xmerl_xpath.string('string(/entry/activity:object-type[1])', doc)
case object_type do
'http://activitystrea.ms/schema/1.0/note' ->
handle_note(doc)
_ ->
Logger.error("Couldn't parse incoming document")
end
end
# TODO
# wire up replies
def handle_note(doc) do
content_html = string_from_xpath("/entry/content[1]", doc)
uri = string_from_xpath("/entry/author/uri[1]", doc)
{:ok, actor} = find_or_make_user(uri)
context = string_from_xpath("/entry/ostatus:conversation[1]", doc) |> String.trim
context = if String.length(context) > 0 do
context
else
ActivityPub.generate_context_id
end
to = [
"https://www.w3.org/ns/activitystreams#Public"
]
mentions = :xmerl_xpath.string('/entry/link[@rel="mentioned" and @ostatus:object-type="http://activitystrea.ms/schema/1.0/person"]', doc)
|> Enum.map(fn(person) -> string_from_xpath("@href", person) end)
to = to ++ mentions
date = string_from_xpath("/entry/published", doc)
object = %{
"type" => "Note",
"to" => to,
"content" => content_html,
"published" => date,
"context" => context,
"actor" => actor.ap_id
}
inReplyTo = string_from_xpath("/entry/thr:in-reply-to[1]/@href", doc)
object = if inReplyTo do
Map.put(object, "inReplyTo", inReplyTo)
else
object
end
ActivityPub.create(to, actor, context, object, %{}, date)
end
def find_or_make_user(uri) do
query = from user in User,
where: user.local == false and fragment("? @> ?", user.info, ^%{uri: uri})
user = Repo.one(query)
if is_nil(user) do
make_user(uri)
else
{:ok, user}
end
end
def make_user(uri) do
with {:ok, info} <- gather_user_info(uri) do
data = %{
local: false,
name: info.name,
- nickname: info.nickname,
+ nickname: info.nickname <> "@" <> info.host,
ap_id: info.uri,
info: info
}
+ # TODO: Make remote user changeset
+ # SHould enforce fqn nickname
Repo.insert(Ecto.Changeset.change(%User{}, data))
end
end
# TODO: Just takes the first one for now.
defp make_avatar_object(author_doc) do
href = string_from_xpath("/author[1]/link[@rel=\"avatar\"]/@href", author_doc)
type = string_from_xpath("/author[1]/link[@rel=\"avatar\"]/@type", author_doc)
if href do
%{
"type" => "Image",
"url" =>
[%{
"type" => "Link",
"mediaType" => type,
"href" => href
}]
}
else
nil
end
end
def gather_user_info(username) do
with {:ok, webfinger_data} <- WebFinger.finger(username),
{:ok, feed_data} <- Websub.gather_feed_data(webfinger_data.topic) do
{:ok, Map.merge(webfinger_data, feed_data) |> Map.put(:fqn, username)}
else e ->
Logger.debug("Couldn't gather info for #{username}")
{:error, e}
end
end
end
diff --git a/lib/pleroma/web/websub/websub.ex b/lib/pleroma/web/websub/websub.ex
index 8e3e0a54e..3fd779fba 100644
--- a/lib/pleroma/web/websub/websub.ex
+++ b/lib/pleroma/web/websub/websub.ex
@@ -1,184 +1,185 @@
defmodule Pleroma.Web.Websub do
alias Pleroma.Repo
alias Pleroma.Web.Websub.{WebsubServerSubscription, WebsubClientSubscription}
alias Pleroma.Web.OStatus.FeedRepresenter
alias Pleroma.Web.{XML, Endpoint, OStatus}
alias Pleroma.Web.Router.Helpers
require Logger
import Ecto.Query
def verify(subscription, getter \\ &HTTPoison.get/3 ) do
challenge = Base.encode16(:crypto.strong_rand_bytes(8))
lease_seconds = NaiveDateTime.diff(subscription.valid_until, subscription.updated_at) |> to_string
params = %{
"hub.challenge": challenge,
"hub.lease_seconds": lease_seconds,
"hub.topic": subscription.topic,
"hub.mode": "subscribe"
}
url = hd(String.split(subscription.callback, "?"))
query = URI.parse(subscription.callback).query || ""
params = Map.merge(params, URI.decode_query(query))
with {:ok, response} <- getter.(url, [], [params: params]),
^challenge <- response.body
do
changeset = Ecto.Changeset.change(subscription, %{state: "active"})
Repo.update(changeset)
else _e ->
changeset = Ecto.Changeset.change(subscription, %{state: "rejected"})
{:ok, subscription } = Repo.update(changeset)
{:error, subscription}
end
end
def publish(topic, user, activity) do
query = from sub in WebsubServerSubscription,
where: sub.topic == ^topic and sub.state == "active"
subscriptions = Repo.all(query)
Enum.each(subscriptions, fn(sub) ->
response = FeedRepresenter.to_simple_form(user, [activity], [user])
|> :xmerl.export_simple(:xmerl_xml)
signature = sign(sub.secret, response)
HTTPoison.post(sub.callback, response, [
{"Content-Type", "application/atom+xml"},
{"X-Hub-Signature", "sha1=#{signature}"}
])
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),
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)
Pleroma.Web.Federator.enqueue(:verify_websub, websub)
{: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
def subscribe(subscriber, subscribed, requester \\ &request_subscription/1) do
topic = subscribed.info["topic"]
# FIXME: Race condition, use transactions
{:ok, subscription} = with subscription when not is_nil(subscription) <- Repo.get_by(WebsubClientSubscription, topic: topic) do
subscribers = [subscriber.ap_id, subscription.subscribers] |> Enum.uniq
change = Ecto.Changeset.change(subscription, %{subscribers: subscribers})
Repo.update(change)
else _e ->
subscription = %WebsubClientSubscription{
topic: topic,
hub: subscribed.info["hub"],
subscribers: [subscriber.ap_id],
state: "requested",
secret: :crypto.strong_rand_bytes(8) |> Base.url_encode64,
user: subscribed
}
Repo.insert(subscription)
end
requester.(subscription)
end
def gather_feed_data(topic, getter \\ &HTTPoison.get/1) do
with {:ok, response} <- getter.(topic),
status_code when status_code in 200..299 <- response.status_code,
body <- response.body,
doc <- XML.parse_document(body),
uri when not is_nil(uri) <- XML.string_from_xpath("/feed/author[1]/uri", doc),
hub when not is_nil(hub) <- XML.string_from_xpath(~S{/feed/link[@rel="hub"]/@href}, doc) do
name = XML.string_from_xpath("/feed/author[1]/name", doc)
preferredUsername = XML.string_from_xpath("/feed/author[1]/poco:preferredUsername", doc)
displayName = XML.string_from_xpath("/feed/author[1]/poco:displayName", doc)
{:ok, %{
uri: uri,
hub: hub,
nickname: preferredUsername || name,
- name: displayName || name
+ name: displayName || name,
+ host: URI.parse(uri).host
}}
else e ->
{:error, e}
end
end
def request_subscription(websub, poster \\ &HTTPoison.post/3, timeout \\ 10_000) do
data = [
"hub.mode": "subscribe",
"hub.topic": websub.topic,
"hub.secret": websub.secret,
"hub.callback": Helpers.websub_url(Endpoint, :websub_subscription_confirmation, websub.id)
]
# This checks once a second if we are confirmed yet
websub_checker = fn ->
helper = fn (helper) ->
:timer.sleep(1000)
websub = Repo.get_by(WebsubClientSubscription, id: websub.id, state: "accepted")
if websub, do: websub, else: helper.(helper)
end
helper.(helper)
end
task = Task.async(websub_checker)
with {:ok, %{status_code: 202}} <- poster.(websub.hub, {:form, data}, ["Content-type": "application/x-www-form-urlencoded"]),
{:ok, websub} <- Task.yield(task, timeout) do
{:ok, websub}
else e ->
Task.shutdown(task)
change = Ecto.Changeset.change(websub, %{state: "rejected"})
{:ok, websub} = Repo.update(change)
Logger.debug("Couldn't confirm subscription: #{inspect(websub)}")
Logger.debug("error: #{inspect(e)}")
{:error, websub}
end
end
end
diff --git a/test/web/ostatus/ostatus_test.exs b/test/web/ostatus/ostatus_test.exs
index 4f396d940..cc0975bb5 100644
--- a/test/web/ostatus/ostatus_test.exs
+++ b/test/web/ostatus/ostatus_test.exs
@@ -1,88 +1,90 @@
defmodule Pleroma.Web.OStatusTest do
use Pleroma.DataCase
alias Pleroma.Web.OStatus
alias Pleroma.Web.XML
test "handle incoming notes" do
incoming = File.read!("test/fixtures/incoming_note_activity.xml")
{:ok, activity} = OStatus.handle_incoming(incoming)
assert activity.data["type"] == "Create"
assert activity.data["object"]["type"] == "Note"
assert activity.data["published"] == "2017-04-23T14:51:03+00:00"
assert activity.data["context"] == "tag:gs.example.org:4040,2017-04-23:objectType=thread:nonce=f09e22f58abd5c7b"
assert "http://pleroma.example.org:4000/users/lain3" in activity.data["to"]
end
test "handle incoming replies" do
incoming = File.read!("test/fixtures/incoming_note_activity_answer.xml")
{:ok, activity} = OStatus.handle_incoming(incoming)
assert activity.data["type"] == "Create"
assert activity.data["object"]["type"] == "Note"
assert activity.data["object"]["inReplyTo"] == "http://pleroma.example.org:4000/objects/55bce8fc-b423-46b1-af71-3759ab4670bc"
assert "http://pleroma.example.org:4000/users/lain5" in activity.data["to"]
end
describe "new remote user creation" do
test "tries to use the information in poco fields" do
# TODO make test local
uri = "https://social.heldscal.la/user/23211"
{:ok, user} = OStatus.find_or_make_user(uri)
user = Repo.get(Pleroma.User, user.id)
assert user.name == "Constance Variable"
- assert user.nickname == "lambadalambda"
+ assert user.nickname == "lambadalambda@social.heldscal.la"
assert user.local == false
assert user.info["uri"] == uri
assert user.ap_id == uri
{:ok, user_again} = OStatus.find_or_make_user(uri)
assert user == user_again
end
end
describe "gathering user info from a user id" do
test "it returns user info in a hash" do
user = "shp@social.heldscal.la"
# TODO: make test local
{:ok, data} = OStatus.gather_user_info(user)
expected = %{
hub: "https://social.heldscal.la/main/push/hub",
magic_key: "RSA.wQ3i9UA0qmAxZ0WTIp4a-waZn_17Ez1pEEmqmqoooRsG1_BvpmOvLN0G2tEcWWxl2KOtdQMCiPptmQObeZeuj48mdsDZ4ArQinexY2hCCTcbV8Xpswpkb8K05RcKipdg07pnI7tAgQ0VWSZDImncL6YUGlG5YN8b5TjGOwk2VG8=.AQAB",
name: "shp",
nickname: "shp",
salmon: "https://social.heldscal.la/main/salmon/user/29191",
subject: "acct:shp@social.heldscal.la",
topic: "https://social.heldscal.la/api/statuses/user_timeline/29191.atom",
uri: "https://social.heldscal.la/user/29191",
+ host: "social.heldscal.la",
fqn: user
}
assert data == expected
end
test "it works with the uri" do
user = "https://social.heldscal.la/user/29191"
# TODO: make test local
{:ok, data} = OStatus.gather_user_info(user)
expected = %{
hub: "https://social.heldscal.la/main/push/hub",
magic_key: "RSA.wQ3i9UA0qmAxZ0WTIp4a-waZn_17Ez1pEEmqmqoooRsG1_BvpmOvLN0G2tEcWWxl2KOtdQMCiPptmQObeZeuj48mdsDZ4ArQinexY2hCCTcbV8Xpswpkb8K05RcKipdg07pnI7tAgQ0VWSZDImncL6YUGlG5YN8b5TjGOwk2VG8=.AQAB",
name: "shp",
nickname: "shp",
salmon: "https://social.heldscal.la/main/salmon/user/29191",
subject: "https://social.heldscal.la/user/29191",
topic: "https://social.heldscal.la/api/statuses/user_timeline/29191.atom",
uri: "https://social.heldscal.la/user/29191",
+ host: "social.heldscal.la",
fqn: user
}
assert data == expected
end
end
end
diff --git a/test/web/websub/websub_test.exs b/test/web/websub/websub_test.exs
index 25c2b8baa..e0d71e16d 100644
--- a/test/web/websub/websub_test.exs
+++ b/test/web/websub/websub_test.exs
@@ -1,168 +1,169 @@
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
alias Pleroma.Web.Router.Helpers
test "a verification of a request that is accepted" do
sub = insert(:websub_subscription)
topic = sub.topic
getter = fn (_path, _headers, options) ->
%{
"hub.challenge": challenge,
"hub.lease_seconds": seconds,
"hub.topic": ^topic,
"hub.mode": "subscribe"
} = Keyword.get(options, :params)
assert String.to_integer(seconds) > 0
{:ok, %HTTPoison.Response{
status_code: 200,
body: challenge
}}
end
{:ok, sub} = Websub.verify(sub, getter)
assert sub.state == "active"
end
test "a verification of a request that doesn't return 200" do
sub = insert(:websub_subscription)
getter = fn (_path, _headers, _options) ->
{:ok, %HTTPoison.Response{
status_code: 500,
body: ""
}}
end
{: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" => "subscribe",
"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" => "subscribe",
"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
def accepting_verifier(subscription) do
{:ok, %{ subscription | state: "accepted" }}
end
test "initiate a subscription for a given user and topic" do
subscriber = insert(:user)
user = insert(:user, %{info: %{ "topic" => "some_topic", "hub" => "some_hub"}})
{:ok, websub} = Websub.subscribe(subscriber, user, &accepting_verifier/1)
assert websub.subscribers == [subscriber.ap_id]
assert websub.topic == "some_topic"
assert websub.hub == "some_hub"
assert is_binary(websub.secret)
assert websub.user == user
assert websub.state == "accepted"
end
test "discovers the hub and canonical url" do
topic = "https://mastodon.social/users/lambadalambda.atom"
getter = fn(^topic) ->
doc = File.read!("test/fixtures/lambadalambda.atom")
{:ok, %{status_code: 200, body: doc}}
end
{:ok, discovered} = Websub.gather_feed_data(topic, getter)
expected = %{
hub: "https://mastodon.social/api/push",
uri: "https://mastodon.social/users/lambadalambda",
nickname: "lambadalambda",
- name: "Critical Value"
+ name: "Critical Value",
+ host: "mastodon.social"
}
assert expected == discovered
end
test "calls the hub, requests topic" do
hub = "https://social.heldscal.la/main/push/hub"
topic = "https://social.heldscal.la/api/statuses/user_timeline/23211.atom"
websub = insert(:websub_client_subscription, %{hub: hub, topic: topic})
poster = fn (^hub, {:form, data}, _headers) ->
assert Keyword.get(data, :"hub.mode") == "subscribe"
assert Keyword.get(data, :"hub.callback") == Helpers.websub_url(Pleroma.Web.Endpoint, :websub_subscription_confirmation, websub.id)
{:ok, %{status_code: 202}}
end
task = Task.async(fn -> Websub.request_subscription(websub, poster) end)
change = Ecto.Changeset.change(websub, %{state: "accepted"})
{:ok, _} = Repo.update(change)
{:ok, websub} = Task.await(task)
assert websub.state == "accepted"
end
test "rejects the subscription if it can't be accepted" do
hub = "https://social.heldscal.la/main/push/hub"
topic = "https://social.heldscal.la/api/statuses/user_timeline/23211.atom"
websub = insert(:websub_client_subscription, %{hub: hub, topic: topic})
poster = fn (^hub, {:form, _data}, _headers) ->
{:ok, %{status_code: 202}}
end
{:error, websub} = Websub.request_subscription(websub, poster, 1000)
assert websub.state == "rejected"
websub = insert(:websub_client_subscription, %{hub: hub, topic: topic})
poster = fn (^hub, {:form, _data}, _headers) ->
{:ok, %{status_code: 400}}
end
{:error, websub} = Websub.request_subscription(websub, poster, 1000)
assert websub.state == "rejected"
end
end

File Metadata

Mime Type
text/x-diff
Expires
Sun, Aug 9, 5:11 AM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1724236
Default Alt Text
(19 KB)

Event Timeline