Rework remote user subscription.

This commit is contained in:
Roger Braun 2017-04-29 19:06:01 +02:00
parent 69922bc724
commit 427bac0966
5 changed files with 70 additions and 67 deletions

View file

@ -37,8 +37,8 @@ def handle_incoming(xml_string) do
def handle_note(doc) do def handle_note(doc) do
content_html = string_from_xpath("/entry/content[1]", doc) content_html = string_from_xpath("/entry/content[1]", doc)
[author] = :xmerl_xpath.string('/entry/author[1]', doc) uri = string_from_xpath("/entry/author/uri[1]", doc)
{:ok, actor} = find_or_make_user(author) {:ok, actor} = find_or_make_user(uri)
context = string_from_xpath("/entry/ostatus:conversation[1]", doc) |> String.trim context = string_from_xpath("/entry/ostatus:conversation[1]", doc) |> String.trim
context = if String.length(context) > 0 do context = if String.length(context) > 0 do
@ -78,43 +78,31 @@ def handle_note(doc) do
ActivityPub.create(to, actor, context, object, %{}, date) ActivityPub.create(to, actor, context, object, %{}, date)
end end
def find_or_make_user(author_doc) do def find_or_make_user(uri) do
{:xmlObj, :string, uri } = :xmerl_xpath.string('string(/author[1]/uri)', author_doc)
query = from user in User, query = from user in User,
where: user.local == false and fragment("? @> ?", user.info, ^%{ostatus_uri: to_string(uri)}) where: user.local == false and fragment("? @> ?", user.info, ^%{uri: uri})
user = Repo.one(query) user = Repo.one(query)
if is_nil(user) do if is_nil(user) do
make_user(author_doc) make_user(uri)
else else
{:ok, user} {:ok, user}
end end
end end
def make_user(author_doc) do def make_user(uri) do
author = string_from_xpath("/author[1]/uri", author_doc) with {:ok, info} <- gather_user_info(uri) do
name = string_from_xpath("/author[1]/name", author_doc)
preferredUsername = string_from_xpath("/author[1]/poco:preferredUsername", author_doc)
displayName = string_from_xpath("/author[1]/poco:displayName", author_doc)
avatar = make_avatar_object(author_doc)
data = %{ data = %{
local: false, local: false,
name: preferredUsername || name, name: info.name,
nickname: displayName || name, nickname: info.nickname,
ap_id: author, ap_id: info.uri,
info: %{ info: info
"ostatus_uri" => author,
"host" => URI.parse(author).host,
"system" => "ostatus"
},
avatar: avatar
} }
Repo.insert(Ecto.Changeset.change(%User{}, data)) Repo.insert(Ecto.Changeset.change(%User{}, data))
end end
end
# TODO: Just takes the first one for now. # TODO: Just takes the first one for now.
defp make_avatar_object(author_doc) do defp make_avatar_object(author_doc) do

View file

@ -42,7 +42,7 @@ def represent_user(user) do
# FIXME: Make this call the host-meta to find the actual address. # FIXME: Make this call the host-meta to find the actual address.
defp webfinger_address(domain) do defp webfinger_address(domain) do
"https://#{domain}/.well-known/webfinger" "//#{domain}/.well-known/webfinger"
end end
defp webfinger_from_xml(doc) do defp webfinger_from_xml(doc) do
@ -61,9 +61,21 @@ defp webfinger_from_xml(doc) do
end end
def finger(account, getter \\ &HTTPoison.get/3) do def finger(account, getter \\ &HTTPoison.get/3) do
[name, domain] = String.split(account, "@") domain = with [_name, domain] <- String.split(account, "@") do
domain
else _e ->
URI.parse(account).host
end
address = webfinger_address(domain) address = webfinger_address(domain)
with {:ok, %{status_code: status_code, body: body}} when status_code in 200..299 <- getter.(address, ["Accept": "application/xrd+xml"], [params: [resource: account]]),
# try https first
response = with {:ok, result} <- getter.("https:" <> address, ["Accept": "application/xrd+xml"], [params: [resource: account]]) do
{:ok, result}
else _ ->
getter.("http:" <> address, ["Accept": "application/xrd+xml"], [params: [resource: account]])
end
with {:ok, %{status_code: status_code, body: body}} when status_code in 200..299 <- response,
doc <- XML.parse_document(body), doc <- XML.parse_document(body),
{:ok, data} <- webfinger_from_xml(doc) do {:ok, data} <- webfinger_from_xml(doc) do
{:ok, data} {:ok, data}

View file

@ -102,19 +102,21 @@ defp valid_topic(%{"hub.topic" => topic}, user) do
end end
end end
def subscribe(user, topic, requester \\ &request_subscription/1) do def subscribe(subscriber, subscribed, requester \\ &request_subscription/1) do
topic = subscribed.info["topic"]
# FIXME: Race condition, use transactions # FIXME: Race condition, use transactions
{:ok, subscription} = with subscription when not is_nil(subscription) <- Repo.get_by(WebsubClientSubscription, topic: topic) do {:ok, subscription} = with subscription when not is_nil(subscription) <- Repo.get_by(WebsubClientSubscription, topic: topic) do
subscribers = [user.ap_id, subscription.subcribers] |> Enum.uniq subscribers = [subscriber.ap_id, subscription.subscribers] |> Enum.uniq
change = Ecto.Changeset.change(subscription, %{subscribers: subscribers}) change = Ecto.Changeset.change(subscription, %{subscribers: subscribers})
Repo.update(change) Repo.update(change)
else _e -> else _e ->
subscription = %WebsubClientSubscription{ subscription = %WebsubClientSubscription{
topic: topic, topic: topic,
subscribers: [user.ap_id], hub: subscribed.info["hub"],
subscribers: [subscriber.ap_id],
state: "requested", state: "requested",
secret: :crypto.strong_rand_bytes(8) |> Base.url_encode64, secret: :crypto.strong_rand_bytes(8) |> Base.url_encode64,
user: user user: subscribed
} }
Repo.insert(subscription) Repo.insert(subscription)
end end

View file

@ -25,40 +25,20 @@ test "handle incoming replies" do
end end
describe "new remote user creation" do describe "new remote user creation" do
test "make new user or find them based on an 'author' xml doc" do
incoming = File.read!("test/fixtures/user_name_only.xml")
doc = XML.parse_document(incoming)
{:ok, user} = OStatus.find_or_make_user(doc)
assert user.name == "lambda"
assert user.nickname == "lambda"
assert user.local == false
assert user.info["ostatus_uri"] == "http://gs.example.org:4040/index.php/user/1"
assert user.info["system"] == "ostatus"
assert user.ap_id == "http://gs.example.org:4040/index.php/user/1"
{:ok, user_again} = OStatus.find_or_make_user(doc)
assert user == user_again
end
test "tries to use the information in poco fields" do test "tries to use the information in poco fields" do
incoming = File.read!("test/fixtures/user_full.xml") # TODO make test local
doc = XML.parse_document(incoming) uri = "https://social.heldscal.la/user/23211"
{:ok, user} = OStatus.find_or_make_user(doc) {:ok, user} = OStatus.find_or_make_user(uri)
user = Repo.get(Pleroma.User, user.id)
assert user.name == "Constance Variable" assert user.name == "Constance Variable"
assert user.nickname == "lambadalambda" assert user.nickname == "lambadalambda"
assert user.local == false assert user.local == false
assert user.info["ostatus_uri"] == "http://gs.example.org:4040/index.php/user/1" assert user.info["uri"] == uri
assert user.info["system"] == "ostatus" assert user.ap_id == uri
assert user.ap_id == "http://gs.example.org:4040/index.php/user/1"
assert List.first(user.avatar["url"])["href"] == "http://gs.example.org:4040/theme/neo-gnu/default-avatar-profile.png" {:ok, user_again} = OStatus.find_or_make_user(uri)
{:ok, user_again} = OStatus.find_or_make_user(doc)
assert user == user_again assert user == user_again
end end
@ -84,5 +64,25 @@ test "it returns user info in a hash" do
} }
assert data == expected assert data == expected
end 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",
fqn: user
}
assert data == expected
end
end end
end end

View file

@ -93,12 +93,13 @@ def accepting_verifier(subscription) do
end end
test "initiate a subscription for a given user and topic" do test "initiate a subscription for a given user and topic" do
user = insert(:user) subscriber = insert(:user)
topic = "http://example.org/some-topic.atom" user = insert(:user, %{info: %{ "topic" => "some_topic", "hub" => "some_hub"}})
{:ok, websub} = Websub.subscribe(user, topic, &accepting_verifier/1) {:ok, websub} = Websub.subscribe(subscriber, user, &accepting_verifier/1)
assert websub.subscribers == [user.ap_id] assert websub.subscribers == [subscriber.ap_id]
assert websub.topic == topic assert websub.topic == "some_topic"
assert websub.hub == "some_hub"
assert is_binary(websub.secret) assert is_binary(websub.secret)
assert websub.user == user assert websub.user == user
assert websub.state == "accepted" assert websub.state == "accepted"