commit
a9b2ad1759
@ -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
|
@ -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) || user).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
|
@ -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
|
@ -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
|
@ -0,0 +1,20 @@
|
||||
defmodule Pleroma.Web.OStatus.UserRepresenter do
|
||||
alias Pleroma.User
|
||||
def to_simple_form(user) do
|
||||
ap_id = to_charlist(user.ap_id)
|
||||
nickname = to_charlist(user.nickname)
|
||||
name = to_charlist(user.name)
|
||||
bio = to_charlist(user.bio)
|
||||
avatar_url = to_charlist(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
|
@ -0,0 +1,73 @@
|
||||
defmodule Pleroma.Web.Salmon do
|
||||
use Bitwise
|
||||
|
||||
def decode(salmon) do
|
||||
{doc, _rest} = :xmerl_scan.string(to_charlist(salmon))
|
||||
|
||||
{:xmlObj, :string, data} = :xmerl_xpath.string('string(//me:data[1])', doc)
|
||||
{:xmlObj, :string, sig} = :xmerl_xpath.string('string(//me:sig[1])', doc)
|
||||
{:xmlObj, :string, alg} = :xmerl_xpath.string('string(//me:alg[1])', doc)
|
||||
{:xmlObj, :string, encoding} = :xmerl_xpath.string('string(//me:encoding[1])', doc)
|
||||
{:xmlObj, :string, type} = :xmerl_xpath.string('string(//me:data[1]/@type)', doc)
|
||||
|
||||
|
||||
{:ok, data} = Base.url_decode64(to_string(data), ignore: :whitespace)
|
||||
{:ok, sig} = Base.url_decode64(to_string(sig), ignore: :whitespace)
|
||||
alg = to_string(alg)
|
||||
encoding = to_string(encoding)
|
||||
type = to_string(type)
|
||||
|
||||
[data, type, encoding, alg, sig]
|
||||
end
|
||||
|
||||
def fetch_magic_key(salmon) do
|
||||
[data, _, _, _, _] = decode(salmon)
|
||||
{doc, _rest} = :xmerl_scan.string(to_charlist(data))
|
||||
{:xmlObj, :string, uri} = :xmerl_xpath.string('string(//author[1]/uri)', doc)
|
||||
|
||||
uri = to_string(uri)
|
||||
base = URI.parse(uri).host
|
||||
|
||||
# TODO: Find out if this endpoint is mandated by the standard.
|
||||
{:ok, response} = HTTPoison.get(base <> "/.well-known/webfinger", ["Accept": "application/xrd+xml"], [params: [resource: uri]])
|
||||
|
||||
{doc, _rest} = :xmerl_scan.string(to_charlist(response.body))
|
||||
|
||||
{:xmlObj, :string, magickey} = :xmerl_xpath.string('string(//Link[@rel="magic-public-key"]/@href)', doc)
|
||||
"data:application/magic-public-key," <> magickey = to_string(magickey)
|
||||
|
||||
magickey
|
||||
end
|
||||
|
||||
def decode_and_validate(magickey, salmon) do
|
||||
[data, type, encoding, alg, sig] = decode(salmon)
|
||||
|
||||
signed_text = [data, type, encoding, alg]
|
||||
|> Enum.map(&Base.url_encode64/1)
|
||||
|> Enum.join(".")
|
||||
|
||||
key = decode_key(magickey)
|
||||
|
||||
verify = :public_key.verify(signed_text, :sha256, sig, key)
|
||||
|
||||
if verify do
|
||||
{:ok, data}
|
||||
else
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
defp decode_key("RSA." <> magickey) do
|
||||
make_integer = fn(bin) ->
|
||||
list = :erlang.binary_to_list(bin)
|
||||
Enum.reduce(list, 0, fn (el, acc) -> (acc <<< 8) ||| el end)
|
||||
end
|
||||
|
||||
[modulus, exponent] = magickey
|
||||
|> String.split(".")
|
||||
|> Enum.map(&Base.url_decode64!/1)
|
||||
|> Enum.map(make_integer)
|
||||
|
||||
{:RSAPublicKey, modulus, exponent}
|
||||
end
|
||||
end
|
@ -0,0 +1,39 @@
|
||||
defmodule Pleroma.Web.WebFinger do
|
||||
alias Pleroma.XmlBuilder
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.OStatus
|
||||
|
||||
def host_meta() do
|
||||
base_url = Pleroma.Web.base_url
|
||||
{
|
||||
:XRD, %{ xmlns: "http://docs.oasis-open.org/ns/xri/xrd-1.0" },
|
||||
{
|
||||
:Link, %{ rel: "lrdd", type: "application/xrd+xml", template: "#{base_url}/.well-known/webfinger?resource={uri}" }
|
||||
}
|
||||
}
|
||||
|> XmlBuilder.to_doc
|
||||
end
|
||||
|
||||
def webfinger(resource) do
|
||||
host = Pleroma.Web.host
|
||||
regex = ~r/acct:(?<username>\w+)@#{host}/
|
||||
case Regex.named_captures(regex, resource) do
|
||||
%{"username" => username} ->
|
||||
user = User.get_cached_by_nickname(username)
|
||||
{:ok, represent_user(user)}
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def represent_user(user) do
|
||||
{
|
||||
:XRD, %{xmlns: "http://docs.oasis-open.org/ns/xri/xrd-1.0"},
|
||||
[
|
||||
{:Subject, "acct:#{user.nickname}@#{Pleroma.Web.host}"},
|
||||
{:Alias, user.ap_id},
|
||||
{:Link, %{rel: "http://schemas.google.com/g/2010#updates-from", type: "application/atom+xml", href: OStatus.feed_path(user)}}
|
||||
]
|
||||
}
|
||||
|> XmlBuilder.to_doc
|
||||
end
|
||||
end
|
@ -0,0 +1,21 @@
|
||||
defmodule Pleroma.Web.WebFinger.WebFingerController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Web.WebFinger
|
||||
|
||||
def host_meta(conn, _params) do
|
||||
xml = WebFinger.host_meta
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/xrd+xml")
|
||||
|> send_resp(200, xml)
|
||||
end
|
||||
|
||||
def webfinger(conn, %{"resource" => resource}) do
|
||||
{:ok, response} = Pleroma.Web.WebFinger.webfinger(resource)
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/xrd+xml")
|
||||
|> send_resp(200, response)
|
||||
end
|
||||
end
|
@ -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
|
@ -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
|
@ -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
|
@ -0,0 +1,42 @@
|
||||
defmodule Pleroma.XmlBuilder do
|
||||
def to_xml({tag, attributes, content}) do
|
||||
open_tag = make_open_tag(tag, attributes)
|
||||
|
||||
content_xml = to_xml(content)
|
||||
|
||||
"<#{open_tag}>#{content_xml}</#{tag}>"
|
||||
end
|
||||
|
||||
def to_xml({tag, %{} = attributes}) do
|
||||
open_tag = make_open_tag(tag, attributes)
|
||||
|
||||
"<#{open_tag} />"
|
||||
end
|
||||
|
||||
def to_xml({tag, content}), do: to_xml({tag, %{}, content})
|
||||
|
||||
def to_xml(content) when is_binary(content) do
|
||||
to_string(content)
|
||||
end
|
||||
|
||||
def to_xml(content) when is_list(content) do
|
||||
for element <- content do
|
||||
to_xml(element)
|
||||
end
|
||||
|> Enum.join
|
||||
end
|
||||
|
||||
def to_xml(%NaiveDateTime{} = time) do
|
||||
NaiveDateTime.to_iso8601(time)
|
||||
end
|
||||
|
||||
def to_doc(content), do: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" <> to_xml(content)
|
||||
|
||||
defp make_open_tag(tag, attributes) do
|
||||
attributes_string = for {attribute, value} <- attributes do
|
||||
"#{attribute}=\"#{value}\""
|
||||
end |> Enum.join(" ")
|
||||
|
||||
Enum.join([tag, attributes_string], " ") |> String.strip
|
||||
end
|
||||
end
|
@ -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
|
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<me:env xmlns:me="http://salmon-protocol.org/ns/magic-env"><me:data type="application/atom+xml">PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiID8-PGVudHJ5IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDA1L0F0b20iIHhtbG5zOnRocj0iaHR0cDovL3B1cmwub3JnL3N5bmRpY2F0aW9uL3RocmVhZC8xLjAiIHhtbG5zOmFjdGl2aXR5PSJodHRwOi8vYWN0aXZpdHlzdHJlYS5tcy9zcGVjLzEuMC8iIHhtbG5zOmdlb3Jzcz0iaHR0cDovL3d3dy5nZW9yc3Mub3JnL2dlb3JzcyIgeG1sbnM6b3N0YXR1cz0iaHR0cDovL29zdGF0dXMub3JnL3NjaGVtYS8xLjAiIHhtbG5zOnBvY289Imh0dHA6Ly9wb3J0YWJsZWNvbnRhY3RzLm5ldC9zcGVjLzEuMCIgeG1sbnM6bWVkaWE9Imh0dHA6Ly9wdXJsLm9yZy9zeW5kaWNhdGlvbi9hdG9tbWVkaWEiIHhtbG5zOnN0YXR1c25ldD0iaHR0cDovL3N0YXR1cy5uZXQvc2NoZW1hL2FwaS8xLyI-CiA8aWQ-dGFnOmdzLmV4YW1wbGUub3JnOjQwNDAsMjAxNy0wNC0yMzpkaXNmYXZvcjoxOjg6MTk3MC0wMS0wMVQwMDowMDowMCswMDowMDwvaWQ-CiA8dGl0bGU-VW5saWtlPC90aXRsZT4KIDxjb250ZW50IHR5cGU9Imh0bWwiPmxhbWJkYSBubyBsb25nZXIgbGlrZXMgaHR0cDovL3BsZXJvbWEuZXhhbXBsZS5vcmc6NDAwMC9vYmplY3RzL2UyODk2ZmMxLTY1OGItNDJhNy1hMzYyLWUyNThkMzkwNmRlOS48L2NvbnRlbnQ-CiA8YWN0aXZpdHk6dmVyYj5odHRwOi8vYWN0aXZpdHlzdHJlYS5tcy9zY2hlbWEvMS4wL3VuZmF2b3JpdGU8L2FjdGl2aXR5OnZlcmI-CiA8cHVibGlzaGVkPjIwMTctMDQtMjNUMTE6NDc6NTUrMDA6MDA8L3B1Ymxpc2hlZD4KIDx1cGRhdGVkPjIwMTctMDQtMjNUMTE6NDc6NTUrMDA6MDA8L3VwZGF0ZWQ-CiA8YXV0aG9yPgogIDxhY3Rpdml0eTpvYmplY3QtdHlwZT5odHRwOi8vYWN0aXZpdHlzdHJlYS5tcy9zY2hlbWEvMS4wL3BlcnNvbjwvYWN0aXZpdHk6b2JqZWN0LXR5cGU-CiAgPHVyaT5odHRwOi8vZ3MuZXhhbXBsZS5vcmc6NDA0MC9pbmRleC5waHAvdXNlci8xPC91cmk-CiAgPG5hbWU-bGFtYmRhPC9uYW1lPgogIDxsaW5rIHJlbD0iYWx0ZXJuYXRlIiB0eXBlPSJ0ZXh0L2h0bWwiIGhyZWY9Imh0dHA6Ly9ncy5leGFtcGxlLm9yZzo0MDQwL2luZGV4LnBocC9sYW1iZGEiLz4KICA8bGluayByZWw9ImF2YXRhciIgdHlwZT0iaW1hZ2UvcG5nIiBtZWRpYTp3aWR0aD0iOTYiIG1lZGlhOmhlaWdodD0iOTYiIGhyZWY9Imh0dHA6Ly9ncy5leGFtcGxlLm9yZzo0MDQwL3RoZW1lL25lby1nbnUvZGVmYXVsdC1hdmF0YXItcHJvZmlsZS5wbmciLz4KICA8bGluayByZWw9ImF2YXRhciIgdHlwZT0iaW1hZ2UvcG5nIiBtZWRpYTp3aWR0aD0iNDgiIG1lZGlhOmhlaWdodD0iNDgiIGhyZWY9Imh0dHA6Ly9ncy5leGFtcGxlLm9yZzo0MDQwL3RoZW1lL25lby1nbnUvZGVmYXVsdC1hdmF0YXItc3RyZWFtLnBuZyIvPgogIDxsaW5rIHJlbD0iYXZhdGFyIiB0eXBlPSJpbWFnZS9wbmciIG1lZGlhOndpZHRoPSIyNCIgbWVkaWE6aGVpZ2h0PSIyNCIgaHJlZj0iaHR0cDovL2dzLmV4YW1wbGUub3JnOjQwNDAvdGhlbWUvbmVvLWdudS9kZWZhdWx0LWF2YXRhci1taW5pLnBuZyIvPgogIDxwb2NvOnByZWZlcnJlZFVzZXJuYW1lPmxhbWJkYTwvcG9jbzpwcmVmZXJyZWRVc2VybmFtZT4KICA8cG9jbzpkaXNwbGF5TmFtZT5sYW1iZGE8L3BvY286ZGlzcGxheU5hbWU-CiAgPGZvbGxvd2VycyB1cmw9Imh0dHA6Ly9ncy5leGFtcGxlLm9yZzo0MDQwL2luZGV4LnBocC9sYW1iZGEvc3Vic2NyaWJlcnMiPjwvZm9sbG93ZXJzPgogPC9hdXRob3I-CiA8YWN0aXZpdHk6b2JqZWN0PgogIDxhY3Rpdml0eTpvYmplY3QtdHlwZT5odHRwOi8vYWN0aXZpdHlzdHJlYS5tcy9zY2hlbWEvMS4wL25vdGU8L2FjdGl2aXR5Om9iamVjdC10eXBlPgogIDxpZD5odHRwOi8vcGxlcm9tYS5leGFtcGxlLm9yZzo0MDAwL29iamVjdHMvZTI4OTZmYzEtNjU4Yi00MmE3LWEzNjItZTI1OGQzOTA2ZGU5PC9pZD4KICA8dGl0bGU-TmV3IG5vdGUgYnkgbGFpbjI8L3RpdGxlPgogIDxjb250ZW50IHR5cGU9Imh0bWwiPkhlbGxvLjwvY29udGVudD4KICA8bGluayByZWw9ImFsdGVybmF0ZSIgdHlwZT0idGV4dC9odG1sIiBocmVmPSJodHRwOi8vcGxlcm9tYS5leGFtcGxlLm9yZzo0MDAwL29iamVjdHMvZTI4OTZmYzEtNjU4Yi00MmE3LWEzNjItZTI1OGQzOTA2ZGU5Ii8-CiAgPHN0YXR1c19uZXQgbm90aWNlX2lkPSI4Ij48L3N0YXR1c19uZXQ-CiA8L2FjdGl2aXR5Om9iamVjdD4KPC9lbnRyeT4K</me:data><me:encoding>base64url</me:encoding><me:alg>RSA-SHA256</me:alg><me:sig>ZXXHgp_ihTZIJnnFiQuJD0TSvo4OIqrpblHHQQwfpCy-85mtTf0QO1LclX3P3Ra8BqAmhs7j9nDxuEGLuVLTt53DvMP-pOjCtWYDKBbEZQtFIVnCcvBzGPW1HmimdN49M3VtAohbhfVilTrApQpGnI6kHvx7G1fQdQxHRtMsdNI=</me:sig></me:env>
|
@ -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
|
@ -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
|
@ -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
|
@ -0,0 +1,31 @@
|
||||
defmodule Pleroma.Web.OStatus.UserRepresenterTest do
|
||||
use Pleroma.DataCase
|
||||
alias Pleroma.Web.OStatus.UserRepresenter
|
||||
|
||||
import Pleroma.Factory
|
||||
alias Pleroma.User
|
||||
|
||||
test "returns a user with id, uri, name and link" do
|
||||
user = build(:user, nickname: "レイン")
|
||||
tuple = UserRepresenter.to_simple_form(user)
|
||||
|
||||
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> to_string
|
||||
|
||||
expected = """
|
||||
<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
|
@ -0,0 +1,19 @@
|
||||
defmodule Pleroma.Web.Salmon.SalmonTest do
|
||||
use Pleroma.DataCase
|
||||
alias Pleroma.Web.Salmon
|
||||
|
||||
@magickey "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwQhh-1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB"
|
||||
|
||||
@wrong_magickey "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwQhh-1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAA"
|
||||
|
||||
test "decodes a salmon" do
|
||||
{:ok, salmon} = File.read("test/fixtures/salmon.xml")
|
||||
{:ok, doc} = Salmon.decode_and_validate(@magickey, salmon)
|
||||
assert Regex.match?(~r/xml/, doc)
|
||||
end
|
||||
|
||||
test "errors on wrong magic key" do
|
||||
{:ok, salmon} = File.read("test/fixtures/salmon.xml")
|
||||
assert Salmon.decode_and_validate(@wrong_magickey, salmon) == :error
|
||||
end
|
||||
end
|
@ -0,0 +1,11 @@
|
||||
defmodule Pleroma.Web.WebFingerTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
describe "host meta" do
|
||||
test "returns a link to the xml lrdd" do
|
||||
host_info = Pleroma.Web.WebFinger.host_meta
|
||||
|
||||
assert String.contains?(host_info, Pleroma.Web.base_url)
|
||||
end
|
||||
end
|
||||
end
|
@ -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
|
@ -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
|
@ -0,0 +1,59 @@
|
||||
defmodule Pleroma.XmlBuilderTest do
|
||||
use Pleroma.DataCase
|
||||
alias Pleroma.XmlBuilder
|
||||
|
||||
test "Build a basic xml string from a tuple" do
|
||||
data = { :feed, %{ xmlns: "http://www.w3.org/2005/Atom"}, "Some content" }
|
||||
|
||||
expected_xml = "<feed xmlns=\"http://www.w3.org/2005/Atom\">Some content</feed>"
|
||||
|
||||
assert XmlBuilder.to_xml(data) == expected_xml
|
||||
end
|
||||
|
||||
test "returns a complete document" do
|
||||
data = { :feed, %{ xmlns: "http://www.w3.org/2005/Atom"}, "Some content" }
|
||||
|
||||
expected_xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><feed xmlns=\"http://www.w3.org/2005/Atom\">Some content</feed>"
|
||||
|
||||
assert XmlBuilder.to_doc(data) == expected_xml
|
||||
end
|
||||
|
||||
test "Works without attributes" do
|
||||
data = {
|
||||
:feed,
|
||||
"Some content"
|
||||
}
|
||||
|
||||
expected_xml = "<feed>Some content</feed>"
|
||||
|
||||
assert XmlBuilder.to_xml(data) == expected_xml
|
||||
end
|
||||
|
||||
test "It works with nested tuples" do
|
||||
data = {
|
||||
:feed,
|
||||
[
|
||||
{:guy, "brush"},
|
||||
{:lament, %{ configuration: "puzzle" }, "pinhead" }
|
||||
]
|
||||
}
|
||||
|
||||
expected_xml = ~s[<feed><guy>brush</guy><lament configuration="puzzle">pinhead</lament></feed>]
|
||||
|
||||
assert XmlBuilder.to_xml(data) == expected_xml
|
||||
end
|
||||
|
||||
test "Represents NaiveDateTime as iso8601" do
|
||||
assert XmlBuilder.to_xml(~N[2000-01-01 13:13:33]) == "2000-01-01T13:13:33"
|
||||
end
|
||||
|
||||
test "Uses self-closing tags when no content is giving" do
|
||||
data = {
|
||||
:link,
|
||||
%{ rel: "self" }
|
||||
}
|
||||
|
||||
expected_xml = ~s[<link rel="self" />]
|
||||
assert XmlBuilder.to_xml(data) == expected_xml
|
||||
end
|
||||
end
|
Loading…
Reference in new issue