Merge branch 'ostatus' into develop
This commit is contained in:
commit
4649ba2d62
24 changed files with 550 additions and 20 deletions
|
@ -30,6 +30,8 @@ config :mime, :types, %{
|
||||||
"application/xrd+xml" => ["xrd+xml"]
|
"application/xrd+xml" => ["xrd+xml"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config :pleroma, :websub_verifier, Pleroma.Web.Websub
|
||||||
|
|
||||||
# Import environment specific config. This must remain at the bottom
|
# Import environment specific config. This must remain at the bottom
|
||||||
# of this file so it overrides the configuration defined above.
|
# of this file so it overrides the configuration defined above.
|
||||||
import_config "#{Mix.env}.exs"
|
import_config "#{Mix.env}.exs"
|
||||||
|
|
|
@ -24,3 +24,5 @@ config :pleroma, Pleroma.Repo,
|
||||||
|
|
||||||
# Reduce hash rounds for testing
|
# Reduce hash rounds for testing
|
||||||
config :comeonin, :pbkdf2_rounds, 1
|
config :comeonin, :pbkdf2_rounds, 1
|
||||||
|
|
||||||
|
config :pleroma, :websub_verifier, Pleroma.Web.WebsubMock
|
||||||
|
|
|
@ -9,6 +9,9 @@ defmodule Pleroma.Web.Endpoint do
|
||||||
# when deploying your static files in production.
|
# when deploying your static files in production.
|
||||||
plug Plug.Static,
|
plug Plug.Static,
|
||||||
at: "/media", from: "uploads", gzip: false
|
at: "/media", from: "uploads", gzip: false
|
||||||
|
plug Plug.Static,
|
||||||
|
at: "/", from: :pleroma,
|
||||||
|
only: ~w(index.html static)
|
||||||
|
|
||||||
# Code reloading can be explicitly enabled under the
|
# Code reloading can be explicitly enabled under the
|
||||||
# :code_reloader configuration of your endpoint.
|
# :code_reloader configuration of your endpoint.
|
||||||
|
|
27
lib/pleroma/web/ostatus/activity_representer.ex
Normal file
27
lib/pleroma/web/ostatus/activity_representer.ex
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
||||||
|
def to_simple_form(%{data: %{"object" => %{"type" => "Note"}}} = activity, user) do
|
||||||
|
h = fn(str) -> [to_charlist(str)] end
|
||||||
|
|
||||||
|
updated_at = activity.updated_at
|
||||||
|
|> NaiveDateTime.to_iso8601
|
||||||
|
inserted_at = activity.inserted_at
|
||||||
|
|> NaiveDateTime.to_iso8601
|
||||||
|
|
||||||
|
attachments = Enum.map(activity.data["object"]["attachment"] || [], fn(attachment) ->
|
||||||
|
url = hd(attachment["url"])
|
||||||
|
{:link, [rel: 'enclosure', href: to_charlist(url["href"]), type: to_charlist(url["mediaType"])], []}
|
||||||
|
end)
|
||||||
|
|
||||||
|
[
|
||||||
|
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/note']},
|
||||||
|
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/post']},
|
||||||
|
{:id, h.(activity.data["object"]["id"])},
|
||||||
|
{:title, ['New note by #{user.nickname}']},
|
||||||
|
{:content, [type: 'html'], h.(activity.data["object"]["content"])},
|
||||||
|
{:published, h.(inserted_at)},
|
||||||
|
{:updated, h.(updated_at)}
|
||||||
|
] ++ attachments
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_simple_form(_,_), do: nil
|
||||||
|
end
|
31
lib/pleroma/web/ostatus/feed_representer.ex
Normal file
31
lib/pleroma/web/ostatus/feed_representer.ex
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
defmodule Pleroma.Web.OStatus.FeedRepresenter do
|
||||||
|
alias Pleroma.Web.OStatus
|
||||||
|
alias Pleroma.Web.OStatus.{UserRepresenter, ActivityRepresenter}
|
||||||
|
|
||||||
|
def to_simple_form(user, activities, users) do
|
||||||
|
most_recent_update = List.first(activities).updated_at
|
||||||
|
|> NaiveDateTime.to_iso8601
|
||||||
|
|
||||||
|
h = fn(str) -> [to_charlist(str)] end
|
||||||
|
|
||||||
|
entries = Enum.map(activities, fn(activity) ->
|
||||||
|
{:entry, ActivityRepresenter.to_simple_form(activity, user)}
|
||||||
|
end)
|
||||||
|
|> Enum.filter(fn ({_, form}) -> form end)
|
||||||
|
|
||||||
|
[{
|
||||||
|
:feed, [
|
||||||
|
xmlns: 'http://www.w3.org/2005/Atom',
|
||||||
|
"xmlns:activity": 'http://activitystrea.ms/spec/1.0/',
|
||||||
|
"xmlns:poco": 'http://portablecontacts.net/spec/1.0'
|
||||||
|
], [
|
||||||
|
{:id, h.(OStatus.feed_path(user))},
|
||||||
|
{:title, ['#{user.nickname}\'s timeline']},
|
||||||
|
{:updated, h.(most_recent_update)},
|
||||||
|
{:link, [rel: 'hub', href: h.(OStatus.pubsub_path(user))], []},
|
||||||
|
{:link, [rel: 'self', href: h.(OStatus.feed_path(user))], []},
|
||||||
|
{:author, UserRepresenter.to_simple_form(user)},
|
||||||
|
] ++ entries
|
||||||
|
}]
|
||||||
|
end
|
||||||
|
end
|
14
lib/pleroma/web/ostatus/ostatus.ex
Normal file
14
lib/pleroma/web/ostatus/ostatus.ex
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
defmodule Pleroma.Web.OStatus do
|
||||||
|
alias Pleroma.Web
|
||||||
|
|
||||||
|
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 user_path(user) do
|
||||||
|
end
|
||||||
|
end
|
31
lib/pleroma/web/ostatus/ostatus_controller.ex
Normal file
31
lib/pleroma/web/ostatus/ostatus_controller.ex
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
defmodule Pleroma.Web.OStatus.OStatusController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.{User, Activity}
|
||||||
|
alias Pleroma.Web.OStatus.FeedRepresenter
|
||||||
|
alias Pleroma.Repo
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
def feed(conn, %{"nickname" => nickname}) do
|
||||||
|
user = User.get_cached_by_nickname(nickname)
|
||||||
|
query = from activity in Activity,
|
||||||
|
where: fragment("? @> ?", activity.data, ^%{actor: user.ap_id}),
|
||||||
|
limit: 20,
|
||||||
|
order_by: [desc: :inserted_at]
|
||||||
|
|
||||||
|
activities = query
|
||||||
|
|> Repo.all
|
||||||
|
|
||||||
|
response = FeedRepresenter.to_simple_form(user, activities, [user])
|
||||||
|
|> :xmerl.export_simple(:xmerl_xml)
|
||||||
|
|> to_string
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/atom+xml")
|
||||||
|
|> send_resp(200, response)
|
||||||
|
end
|
||||||
|
|
||||||
|
def temp(conn, params) do
|
||||||
|
IO.inspect(params)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,14 +1,20 @@
|
||||||
defmodule Pleroma.Web.OStatus.UserRepresenter do
|
defmodule Pleroma.Web.OStatus.UserRepresenter do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
def to_tuple(user, wrapper \\ :author) do
|
def to_simple_form(user) do
|
||||||
{
|
ap_id = to_charlist(user.ap_id)
|
||||||
wrapper, [
|
nickname = to_charlist(user.nickname)
|
||||||
{ :id, user.ap_id },
|
name = to_charlist(user.name)
|
||||||
{ :"activity:object", "http://activitystrea.ms/schema/1.0/person" },
|
bio = to_charlist(user.bio)
|
||||||
{ :uri, user.ap_id },
|
avatar_url = to_charlist(User.avatar_url(user))
|
||||||
{ :name, user.nickname },
|
[
|
||||||
{ :link, %{rel: "avatar", href: User.avatar_url(user)}}
|
{ :id, [ap_id] },
|
||||||
]
|
{ :"activity:object", ['http://activitystrea.ms/schema/1.0/person'] },
|
||||||
}
|
{ :uri, [ap_id] },
|
||||||
|
{ :"poco:preferredUsername", [nickname] },
|
||||||
|
{ :"poco:displayName", [name] },
|
||||||
|
{ :"poco:note", [bio] },
|
||||||
|
{ :name, [nickname] },
|
||||||
|
{ :link, [rel: 'avatar', href: avatar_url], []}
|
||||||
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -66,10 +66,31 @@ defmodule Pleroma.Web.Router do
|
||||||
post "/qvitter/update_avatar", TwitterAPI.Controller, :update_avatar
|
post "/qvitter/update_avatar", TwitterAPI.Controller, :update_avatar
|
||||||
end
|
end
|
||||||
|
|
||||||
|
pipeline :ostatus do
|
||||||
|
plug :accepts, ["xml", "atom"]
|
||||||
|
end
|
||||||
|
|
||||||
|
scope "/", Pleroma.Web do
|
||||||
|
pipe_through :ostatus
|
||||||
|
|
||||||
|
get "/users/:nickname/feed", OStatus.OStatusController, :feed
|
||||||
|
post "/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request
|
||||||
|
end
|
||||||
|
|
||||||
scope "/.well-known", Pleroma.Web do
|
scope "/.well-known", Pleroma.Web do
|
||||||
pipe_through :well_known
|
pipe_through :well_known
|
||||||
|
|
||||||
get "/host-meta", WebFinger.WebFingerController, :host_meta
|
get "/host-meta", WebFinger.WebFingerController, :host_meta
|
||||||
get "/webfinger", WebFinger.WebFingerController, :webfinger
|
get "/webfinger", WebFinger.WebFingerController, :webfinger
|
||||||
end
|
end
|
||||||
|
|
||||||
|
scope "/", Fallback do
|
||||||
|
get "/*path", RedirectController, :redirector
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule Fallback.RedirectController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
def redirector(conn, _params), do: send_file(conn, 200, "priv/static/index.html")
|
||||||
end
|
end
|
||||||
|
|
|
@ -66,7 +66,9 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
||||||
end
|
end
|
||||||
|
|
||||||
with {:ok, activity} <- ActivityPub.insert(activity) do
|
with {:ok, activity} <- ActivityPub.insert(activity) do
|
||||||
add_conversation_id(activity)
|
{:ok, activity} = add_conversation_id(activity)
|
||||||
|
Pleroma.Web.Websub.publish(Pleroma.Web.OStatus.feed_path(user), user, activity)
|
||||||
|
{:ok, activity}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
defmodule Pleroma.Web.WebFinger do
|
defmodule Pleroma.Web.WebFinger do
|
||||||
alias Pleroma.XmlBuilder
|
alias Pleroma.XmlBuilder
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.OStatus
|
||||||
|
|
||||||
def host_meta() do
|
def host_meta() do
|
||||||
base_url = Pleroma.Web.base_url
|
base_url = Pleroma.Web.base_url
|
||||||
|
@ -30,7 +31,7 @@ defmodule Pleroma.Web.WebFinger do
|
||||||
[
|
[
|
||||||
{:Subject, "acct:#{user.nickname}@#{Pleroma.Web.host}"},
|
{:Subject, "acct:#{user.nickname}@#{Pleroma.Web.host}"},
|
||||||
{:Alias, user.ap_id},
|
{:Alias, user.ap_id},
|
||||||
{:Link, %{rel: "http://schemas.google.com/g/2010#updates-from", type: "application/atom+xml", href: "#{user.ap_id}.atom"}}
|
{:Link, %{rel: "http://schemas.google.com/g/2010#updates-from", type: "application/atom+xml", href: OStatus.feed_path(user)}}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|> XmlBuilder.to_doc
|
|> XmlBuilder.to_doc
|
||||||
|
|
102
lib/pleroma/web/websub/websub.ex
Normal file
102
lib/pleroma/web/websub/websub.ex
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
defmodule Pleroma.Web.Websub do
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.Web.Websub.WebsubServerSubscription
|
||||||
|
alias Pleroma.Web.OStatus.FeedRepresenter
|
||||||
|
alias Pleroma.Web.OStatus
|
||||||
|
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
@websub_verifier Application.get_env(:pleroma, :websub_verifier)
|
||||||
|
|
||||||
|
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 = :crypto.hmac(:sha, sub.secret, response) |> Base.encode16
|
||||||
|
|
||||||
|
HTTPoison.post(sub.callback, response, [
|
||||||
|
{"Content-Type", "application/atom+xml"},
|
||||||
|
{"X-Hub-Signature", "sha1=#{signature}"}
|
||||||
|
])
|
||||||
|
end)
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Just spawn that for now, maybe pool later.
|
||||||
|
spawn(fn -> @websub_verifier.verify(websub) end)
|
||||||
|
|
||||||
|
{: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
|
||||||
|
end
|
18
lib/pleroma/web/websub/websub_controller.ex
Normal file
18
lib/pleroma/web/websub/websub_controller.ex
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
defmodule Pleroma.Web.Websub.WebsubController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.Websub
|
||||||
|
|
||||||
|
def websub_subscription_request(conn, %{"nickname" => nickname} = params) do
|
||||||
|
user = User.get_cached_by_nickname(nickname)
|
||||||
|
|
||||||
|
with {:ok, _websub} <- Websub.incoming_subscription_request(user, params)
|
||||||
|
do
|
||||||
|
conn
|
||||||
|
|> send_resp(202, "Accepted")
|
||||||
|
else {:error, reason} ->
|
||||||
|
conn
|
||||||
|
|> send_resp(500, reason)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
13
lib/pleroma/web/websub/websub_server_subscription.ex
Normal file
13
lib/pleroma/web/websub/websub_server_subscription.ex
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
defmodule Pleroma.Web.Websub.WebsubServerSubscription do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
schema "websub_server_subscriptions" do
|
||||||
|
field :topic, :string
|
||||||
|
field :callback, :string
|
||||||
|
field :secret, :string
|
||||||
|
field :valid_until, :naive_datetime
|
||||||
|
field :state, :string
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
end
|
1
mix.exs
1
mix.exs
|
@ -39,6 +39,7 @@ defmodule Pleroma.Mixfile do
|
||||||
{:html_sanitize_ex, "~> 1.0.0"},
|
{:html_sanitize_ex, "~> 1.0.0"},
|
||||||
{:calendar, "~> 0.16.1"},
|
{:calendar, "~> 0.16.1"},
|
||||||
{:cachex, "~> 2.1"},
|
{:cachex, "~> 2.1"},
|
||||||
|
{:httpoison, "~> 0.11.1"},
|
||||||
{:ex_machina, "~> 2.0", only: :test},
|
{:ex_machina, "~> 2.0", only: :test},
|
||||||
{:mix_test_watch, "~> 0.2", only: :dev}]
|
{:mix_test_watch, "~> 0.2", only: :dev}]
|
||||||
end
|
end
|
||||||
|
|
1
mix.lock
1
mix.lock
|
@ -18,6 +18,7 @@
|
||||||
"gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [:mix], []},
|
"gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [:mix], []},
|
||||||
"hackney": {:hex, :hackney, "1.7.1", "e238c52c5df3c3b16ce613d3a51c7220a784d734879b1e231c9babd433ac1cb4", [:rebar3], [{:certifi, "1.0.0", [hex: :certifi, optional: false]}, {:idna, "4.0.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, optional: false]}]},
|
"hackney": {:hex, :hackney, "1.7.1", "e238c52c5df3c3b16ce613d3a51c7220a784d734879b1e231c9babd433ac1cb4", [:rebar3], [{:certifi, "1.0.0", [hex: :certifi, optional: false]}, {:idna, "4.0.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, optional: false]}]},
|
||||||
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.0.1", "2572e7122c78ab7e57b613e7c7f5e42bf9b3c25e430e32f23f1413d86db8a0af", [:mix], [{:mochiweb, "~> 2.12.2", [hex: :mochiweb, optional: false]}]},
|
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.0.1", "2572e7122c78ab7e57b613e7c7f5e42bf9b3c25e430e32f23f1413d86db8a0af", [:mix], [{:mochiweb, "~> 2.12.2", [hex: :mochiweb, optional: false]}]},
|
||||||
|
"httpoison": {:hex, :httpoison, "0.11.1", "d06c571274c0e77b6cc50e548db3fd7779f611fbed6681fd60a331f66c143a0b", [:mix], [{:hackney, "~> 1.7.0", [hex: :hackney, optional: false]}]},
|
||||||
"idna": {:hex, :idna, "4.0.0", "10aaa9f79d0b12cf0def53038547855b91144f1bfcc0ec73494f38bb7b9c4961", [:rebar3], []},
|
"idna": {:hex, :idna, "4.0.0", "10aaa9f79d0b12cf0def53038547855b91144f1bfcc0ec73494f38bb7b9c4961", [:rebar3], []},
|
||||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []},
|
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []},
|
||||||
"mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], []},
|
"mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], []},
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateWebsubServerSubscription do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:websub_server_subscriptions) do
|
||||||
|
add :topic, :string
|
||||||
|
add :callback, :string
|
||||||
|
add :secret, :string
|
||||||
|
add :valid_until, :naive_datetime
|
||||||
|
add :state, :string
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,7 +3,7 @@ defmodule Pleroma.Factory do
|
||||||
|
|
||||||
def user_factory do
|
def user_factory do
|
||||||
user = %Pleroma.User{
|
user = %Pleroma.User{
|
||||||
name: sequence(:name, &"Test User #{&1}"),
|
name: sequence(:name, &"Test テスト User #{&1}"),
|
||||||
email: sequence(:email, &"user#{&1}@example.com"),
|
email: sequence(:email, &"user#{&1}@example.com"),
|
||||||
nickname: sequence(:nickname, &"nick#{&1}"),
|
nickname: sequence(:nickname, &"nick#{&1}"),
|
||||||
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
|
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
|
||||||
|
@ -64,4 +64,14 @@ defmodule Pleroma.Factory do
|
||||||
data: data
|
data: data
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def websub_subscription_factory do
|
||||||
|
%Pleroma.Web.Websub.WebsubServerSubscription{
|
||||||
|
topic: "http://example.org",
|
||||||
|
callback: "http://example/org/callback",
|
||||||
|
secret: "here's a secret",
|
||||||
|
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now, 100),
|
||||||
|
state: "requested"
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
43
test/web/ostatus/activity_representer_test.exs
Normal file
43
test/web/ostatus/activity_representer_test.exs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
defmodule Pleroma.Web.OStatus.ActivityRepresenterTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Web.OStatus.ActivityRepresenter
|
||||||
|
alias Pleroma.{User, Activity}
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "a note activity" do
|
||||||
|
note_activity = insert(:note_activity)
|
||||||
|
updated_at = note_activity.updated_at
|
||||||
|
|> NaiveDateTime.to_iso8601
|
||||||
|
inserted_at = note_activity.inserted_at
|
||||||
|
|> NaiveDateTime.to_iso8601
|
||||||
|
|
||||||
|
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
||||||
|
|
||||||
|
expected = """
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<id>#{note_activity.data["object"]["id"]}</id>
|
||||||
|
<title>New note by #{user.nickname}</title>
|
||||||
|
<content type="html">#{note_activity.data["object"]["content"]}</content>
|
||||||
|
<published>#{inserted_at}</published>
|
||||||
|
<updated>#{updated_at}</updated>
|
||||||
|
"""
|
||||||
|
|
||||||
|
tuple = ActivityRepresenter.to_simple_form(note_activity, user)
|
||||||
|
|
||||||
|
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary
|
||||||
|
|
||||||
|
assert clean(res) == clean(expected)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "an unknown activity" do
|
||||||
|
tuple = ActivityRepresenter.to_simple_form(%Activity{}, nil)
|
||||||
|
assert is_nil(tuple)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp clean(string) do
|
||||||
|
String.replace(string, ~r/\s/, "")
|
||||||
|
end
|
||||||
|
end
|
45
test/web/ostatus/feed_representer_test.exs
Normal file
45
test/web/ostatus/feed_representer_test.exs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
defmodule Pleroma.Web.OStatus.FeedRepresenterTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
import Pleroma.Factory
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.OStatus.{FeedRepresenter, UserRepresenter, ActivityRepresenter}
|
||||||
|
alias Pleroma.Web.OStatus
|
||||||
|
|
||||||
|
test "returns a feed of the last 20 items of the user" do
|
||||||
|
note_activity = insert(:note_activity)
|
||||||
|
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
||||||
|
|
||||||
|
tuple = FeedRepresenter.to_simple_form(user, [note_activity], [user])
|
||||||
|
|
||||||
|
most_recent_update = note_activity.updated_at
|
||||||
|
|> NaiveDateTime.to_iso8601
|
||||||
|
|
||||||
|
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> to_string
|
||||||
|
user_xml = UserRepresenter.to_simple_form(user)
|
||||||
|
|> :xmerl.export_simple_content(:xmerl_xml)
|
||||||
|
|
||||||
|
entry_xml = ActivityRepresenter.to_simple_form(note_activity, user)
|
||||||
|
|> :xmerl.export_simple_content(:xmerl_xml)
|
||||||
|
|
||||||
|
expected = """
|
||||||
|
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0">
|
||||||
|
<id>#{OStatus.feed_path(user)}</id>
|
||||||
|
<title>#{user.nickname}'s timeline</title>
|
||||||
|
<updated>#{most_recent_update}</updated>
|
||||||
|
<link rel="hub" href="#{OStatus.pubsub_path(user)}" />
|
||||||
|
<link rel="self" href="#{OStatus.feed_path(user)}" />
|
||||||
|
<author>
|
||||||
|
#{user_xml}
|
||||||
|
</author>
|
||||||
|
<entry>
|
||||||
|
#{entry_xml}
|
||||||
|
</entry>
|
||||||
|
</feed>
|
||||||
|
"""
|
||||||
|
assert clean(res) == clean(expected)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp clean(string) do
|
||||||
|
String.replace(string, ~r/\s/, "")
|
||||||
|
end
|
||||||
|
end
|
15
test/web/ostatus/ostatus_controller_test.exs
Normal file
15
test/web/ostatus/ostatus_controller_test.exs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pleroma.Web.OStatus.OStatusControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase
|
||||||
|
import Pleroma.Factory
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
test "gets a feed", %{conn: conn} do
|
||||||
|
note_activity = insert(:note_activity)
|
||||||
|
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
||||||
|
|
||||||
|
conn = conn
|
||||||
|
|> get("/users/#{user.nickname}/feed.atom")
|
||||||
|
|
||||||
|
assert response(conn, 200)
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,15 +3,29 @@ defmodule Pleroma.Web.OStatus.UserRepresenterTest do
|
||||||
alias Pleroma.Web.OStatus.UserRepresenter
|
alias Pleroma.Web.OStatus.UserRepresenter
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
test "returns a user with id, uri, name and link" do
|
test "returns a user with id, uri, name and link" do
|
||||||
user = build(:user)
|
user = build(:user, nickname: "レイン")
|
||||||
tuple = UserRepresenter.to_tuple(user)
|
tuple = UserRepresenter.to_simple_form(user)
|
||||||
{:author, author} = tuple
|
|
||||||
|
|
||||||
[:id, :uri, :name, :link]
|
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> to_string
|
||||||
|> Enum.each(fn (tag) ->
|
|
||||||
assert Enum.find(author, fn(e) -> tag == elem(e, 0) end)
|
expected = """
|
||||||
end)
|
<id>#{user.ap_id}</id>
|
||||||
|
<activity:object>http://activitystrea.ms/schema/1.0/person</activity:object>
|
||||||
|
<uri>#{user.ap_id}</uri>
|
||||||
|
<poco:preferredUsername>#{user.nickname}</poco:preferredUsername>
|
||||||
|
<poco:displayName>#{user.name}</poco:displayName>
|
||||||
|
<poco:note>#{user.bio}</poco:note>
|
||||||
|
<name>#{user.nickname}</name>
|
||||||
|
<link rel="avatar" href="#{User.avatar_url(user)}" />
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert clean(res) == clean(expected)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp clean(string) do
|
||||||
|
String.replace(string, ~r/\s/, "")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
23
test/web/websub/websub_controller_test.exs
Normal file
23
test/web/websub/websub_controller_test.exs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
defmodule Pleroma.Web.Websub.WebsubControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "websub subscription request", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
path = Pleroma.Web.OStatus.pubsub_path(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"
|
||||||
|
}
|
||||||
|
|
||||||
|
conn = conn
|
||||||
|
|> post(path, data)
|
||||||
|
|
||||||
|
assert response(conn, 202) == "Accepted"
|
||||||
|
end
|
||||||
|
end
|
90
test/web/websub/websub_test.exs
Normal file
90
test/web/websub/websub_test.exs
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
Loading…
Reference in a new issue