- <%= gettext "Comments" %>
+ <%= gettext "Comments" %>
<%= for comment <- @report.comments do %>
- <%= comment.text %>
+ <%= HtmlSanitizeEx.strip_tags(comment.text) %>
<% end %>
diff --git a/lib/mobilizon_web/templates/email/report.text.eex b/lib/mobilizon_web/templates/email/report.text.eex
index dae6e72fd..d054fa429 100644
--- a/lib/mobilizon_web/templates/email/report.text.eex
+++ b/lib/mobilizon_web/templates/email/report.text.eex
@@ -4,20 +4,26 @@
<%= if Map.has_key?(@report, :event) do %>
<%= gettext "Event" %>
+
<%= @report.event.title %>
<% end %>
+
<%= if Map.has_key?(@report, :comments) && length(@report.comments) > 0 do %>
<%= gettext "Comments" %>
+
<%= for comment <- @report.comments do %>
<%= comment.text %>
<% end %>
<% end %>
+
<%= if @report.content do %>
<%= gettext "Reason" %>
+
<%= @report.content %>
<% end %>
+
View the report: <%= moderation_report_url(MobilizonWeb.Endpoint, :index, @report.id) %>
diff --git a/lib/mobilizon_web/upload.ex b/lib/mobilizon_web/upload.ex
index 3ba692af1..5920ce586 100644
--- a/lib/mobilizon_web/upload.ex
+++ b/lib/mobilizon_web/upload.ex
@@ -148,6 +148,21 @@ defmodule MobilizonWeb.Upload do
end
end
+ defp prepare_upload(%{body: body, name: name} = _file, opts) do
+ with :ok <- check_binary_size(body, opts.size_limit),
+ tmp_path <- tempfile_for_image(body),
+ {:ok, content_type, name} <- MIME.file_mime_type(tmp_path, name) do
+ {:ok,
+ %__MODULE__{
+ id: UUID.generate(),
+ name: name,
+ tempfile: tmp_path,
+ content_type: content_type,
+ size: byte_size(body)
+ }}
+ end
+ end
+
defp check_file_size(path, size_limit) when is_integer(size_limit) and size_limit > 0 do
with {:ok, %{size: size}} <- File.stat(path),
true <- size <= size_limit do
@@ -160,6 +175,23 @@ defmodule MobilizonWeb.Upload do
defp check_file_size(_, _), do: :ok
+ defp check_binary_size(binary, size_limit)
+ when is_integer(size_limit) and size_limit > 0 and byte_size(binary) >= size_limit do
+ {:error, :file_too_large}
+ end
+
+ defp check_binary_size(_, _), do: :ok
+
+ # Creates a tempfile using the Plug.Upload Genserver which cleans them up
+ # automatically.
+ defp tempfile_for_image(data) do
+ {:ok, tmp_path} = Plug.Upload.random_file("temp_files")
+ {:ok, tmp_file} = File.open(tmp_path, [:write, :raw, :binary])
+ IO.binwrite(tmp_file, data)
+
+ tmp_path
+ end
+
defp url_from_spec(%__MODULE__{name: name}, base_url, {:file, path}) do
path =
URI.encode(path, &char_unescaped?/1) <>
diff --git a/lib/mobilizon_web/views/activity_pub/actor_view.ex b/lib/mobilizon_web/views/activity_pub/actor_view.ex
index e384b95d6..284e12271 100644
--- a/lib/mobilizon_web/views/activity_pub/actor_view.ex
+++ b/lib/mobilizon_web/views/activity_pub/actor_view.ex
@@ -4,44 +4,13 @@ defmodule MobilizonWeb.ActivityPub.ActorView do
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Service.ActivityPub
- alias Mobilizon.Service.ActivityPub.{Activity, Utils}
+ alias Mobilizon.Service.ActivityPub.{Activity, Utils, Convertible}
@private_visibility_empty_collection %{elements: [], total: 0}
def render("actor.json", %{actor: actor}) do
- public_key = Utils.pem_to_public_key_pem(actor.keys)
-
- %{
- "id" => actor.url,
- "type" => to_string(actor.type),
- "following" => actor.following_url,
- "followers" => actor.followers_url,
- "inbox" => actor.inbox_url,
- "outbox" => actor.outbox_url,
- "preferredUsername" => actor.preferred_username,
- "name" => actor.name,
- "summary" => actor.summary,
- "url" => actor.url,
- "manuallyApprovesFollowers" => actor.manually_approves_followers,
- "publicKey" => %{
- "id" => "#{actor.url}#main-key",
- "owner" => actor.url,
- "publicKeyPem" => public_key
- },
- # TODO : Make have actors have an uuid
- # "uuid" => actor.uuid
- "endpoints" => %{
- "sharedInbox" => actor.shared_inbox_url
- }
- # "icon" => %{
- # "type" => "Image",
- # "url" => User.avatar_url(actor)
- # },
- # "image" => %{
- # "type" => "Image",
- # "url" => User.banner_url(actor)
- # }
- }
+ actor
+ |> Convertible.model_to_as()
|> Map.merge(Utils.make_json_ld_header())
end
diff --git a/lib/mobilizon_web/views/page_view.ex b/lib/mobilizon_web/views/page_view.ex
index e3eea5c2c..33b01cc74 100644
--- a/lib/mobilizon_web/views/page_view.ex
+++ b/lib/mobilizon_web/views/page_view.ex
@@ -4,69 +4,34 @@ defmodule MobilizonWeb.PageView do
"""
use MobilizonWeb, :view
alias Mobilizon.Actors.Actor
- alias Mobilizon.Service.ActivityPub.{Converter, Utils}
+ alias Mobilizon.Tombstone
+ alias Mobilizon.Service.ActivityPub.{Convertible, Utils}
alias Mobilizon.Service.Metadata
alias Mobilizon.Service.MetadataUtils
alias Mobilizon.Service.Metadata.Instance
+ alias Mobilizon.Events.{Comment, Event}
- def render("actor.activity-json", %{conn: %{assigns: %{object: actor}}}) do
- public_key = Utils.pem_to_public_key_pem(actor.keys)
-
- %{
- "id" => Actor.build_url(actor.preferred_username, :page),
- "type" => "Person",
- "following" => Actor.build_url(actor.preferred_username, :following),
- "followers" => Actor.build_url(actor.preferred_username, :followers),
- "inbox" => Actor.build_url(actor.preferred_username, :inbox),
- "outbox" => Actor.build_url(actor.preferred_username, :outbox),
- "preferredUsername" => actor.preferred_username,
- "name" => actor.name,
- "summary" => actor.summary,
- "url" => actor.url,
- "manuallyApprovesFollowers" => actor.manually_approves_followers,
- "publicKey" => %{
- "id" => "#{actor.url}#main-key",
- "owner" => actor.url,
- "publicKeyPem" => public_key
- },
- # TODO : Make have actors have an uuid
- # "uuid" => actor.uuid
- "endpoints" => %{
- "sharedInbox" => actor.shared_inbox_url
- }
- # "icon" => %{
- # "type" => "Image",
- # "url" => User.avatar_url(actor)
- # },
- # "image" => %{
- # "type" => "Image",
- # "url" => User.banner_url(actor)
- # }
- }
+ def render("actor.activity-json", %{conn: %{assigns: %{object: %Actor{} = actor}}}) do
+ actor
+ |> Convertible.model_to_as()
|> Map.merge(Utils.make_json_ld_header())
end
- def render("event.activity-json", %{conn: %{assigns: %{object: event}}}) do
+ def render("event.activity-json", %{conn: %{assigns: %{object: %Event{} = event}}}) do
event
- |> Converter.Event.model_to_as()
+ |> Convertible.model_to_as()
|> Map.merge(Utils.make_json_ld_header())
end
- def render("comment.activity-json", %{conn: %{assigns: %{object: comment}}}) do
- comment = Converter.Comment.model_to_as(comment)
+ def render("event.activity-json", %{conn: %{assigns: %{object: %Tombstone{} = event}}}) do
+ event
+ |> Convertible.model_to_as()
+ |> Map.merge(Utils.make_json_ld_header())
+ end
- %{
- "actor" => comment["actor"],
- "uuid" => comment["uuid"],
- # The activity should have attributedTo, not the comment itself
- # "attributedTo" => comment.attributed_to,
- "type" => "Note",
- "id" => comment["id"],
- "content" => comment["content"],
- "mediaType" => "text/html"
- # "published" => Timex.format!(comment.inserted_at, "{ISO:Extended}"),
- # "updated" => Timex.format!(comment.updated_at, "{ISO:Extended}")
- }
+ def render("comment.activity-json", %{conn: %{assigns: %{object: %Comment{} = comment}}}) do
+ comment
+ |> Convertible.model_to_as()
|> Map.merge(Utils.make_json_ld_header())
end
@@ -74,7 +39,9 @@ defmodule MobilizonWeb.PageView do
when page in ["actor.html", "event.html", "comment.html"] do
with {:ok, index_content} <- File.read(index_file_path()) do
tags = object |> Metadata.build_tags() |> MetadataUtils.stringify_tags()
- index_content = String.replace(index_content, "", tags)
+
+ index_content = replace_meta(index_content, tags)
+
{:safe, index_content}
end
end
@@ -82,7 +49,9 @@ defmodule MobilizonWeb.PageView do
def render("index.html", _assigns) do
with {:ok, index_content} <- File.read(index_file_path()) do
tags = Instance.build_tags() |> MetadataUtils.stringify_tags()
- index_content = String.replace(index_content, "", tags)
+
+ index_content = replace_meta(index_content, tags)
+
{:safe, index_content}
end
end
@@ -90,4 +59,11 @@ defmodule MobilizonWeb.PageView do
defp index_file_path do
Path.join(Application.app_dir(:mobilizon, "priv/static"), "index.html")
end
+
+ # TODO: Find why it's different in dev/prod and during tests
+ defp replace_meta(index_content, tags) do
+ index_content
+ |> String.replace("", tags)
+ |> String.replace("", tags)
+ end
end
diff --git a/lib/service/activity_pub/activity_pub.ex b/lib/service/activity_pub/activity_pub.ex
index 8e3d4a0ee..4c612c803 100644
--- a/lib/service/activity_pub/activity_pub.ex
+++ b/lib/service/activity_pub/activity_pub.ex
@@ -11,7 +11,7 @@ defmodule Mobilizon.Service.ActivityPub do
import Mobilizon.Service.ActivityPub.Utils
import Mobilizon.Service.ActivityPub.Visibility
- alias Mobilizon.{Actors, Config, Events, Reports, Users}
+ alias Mobilizon.{Actors, Config, Events, Reports, Users, Share}
alias Mobilizon.Actors.{Actor, Follower}
alias Mobilizon.Events.{Comment, Event, Participant}
alias Mobilizon.Reports.Report
@@ -50,6 +50,15 @@ defmodule Mobilizon.Service.ActivityPub do
def fetch_object_from_url(url) do
Logger.info("Fetching object from url #{url}")
+ date = Mobilizon.Service.HTTPSignatures.Signature.generate_date_header()
+
+ headers =
+ [{:Accept, "application/activity+json"}]
+ |> maybe_date_fetch(date)
+ |> sign_fetch(url, date)
+
+ Logger.debug("Fetch headers: #{inspect(headers)}")
+
with {:not_http, true} <- {:not_http, String.starts_with?(url, "http")},
{:existing_event, nil} <- {:existing_event, Events.get_event_by_url(url)},
{:existing_comment, nil} <- {:existing_comment, Events.get_comment_from_url(url)},
@@ -58,12 +67,13 @@ defmodule Mobilizon.Service.ActivityPub do
{:ok, %{body: body, status_code: code}} when code in 200..299 <-
HTTPoison.get(
url,
- [Accept: "application/activity+json"],
+ headers,
follow_redirect: true,
timeout: 10_000,
recv_timeout: 20_000
),
{:ok, data} <- Jason.decode(body),
+ {:origin_check, true} <- {:origin_check, origin_check?(url, data)},
params <- %{
"type" => "Create",
"to" => data["to"],
@@ -95,6 +105,10 @@ defmodule Mobilizon.Service.ActivityPub do
{:existing_actor, {:ok, %Actor{url: actor_url}}} ->
{:ok, Actors.get_actor_by_url!(actor_url, true)}
+ {:origin_check, false} ->
+ Logger.warn("Object origin check failed")
+ {:error, "Object origin check failed"}
+
e ->
{:error, e}
end
@@ -114,9 +128,9 @@ defmodule Mobilizon.Service.ActivityPub do
{:ok, %Actor{} = actor} ->
{:ok, actor}
- _ ->
+ err ->
Logger.warn("Could not fetch by AP id")
-
+ Logger.debug(inspect(err))
{:error, "Could not fetch by AP id"}
end
end
@@ -184,11 +198,13 @@ defmodule Mobilizon.Service.ActivityPub do
end
end
- def accept(type, entity, args, local \\ false, additional \\ %{}) do
+ def accept(type, entity, local \\ true, additional \\ %{}) do
+ Logger.debug("We're accepting something")
+
{:ok, entity, update_data} =
case type do
- :join -> accept_join(entity, args, additional)
- :follow -> accept_follow(entity, args, additional)
+ :join -> accept_join(entity, additional)
+ :follow -> accept_follow(entity, additional)
end
with {:ok, activity} <- create_activity(update_data, local),
@@ -202,63 +218,24 @@ defmodule Mobilizon.Service.ActivityPub do
end
end
- def reject(%{to: to, actor: actor, object: object} = params, activity_wrapper_id \\ nil) do
- # only accept false as false value
- local = !(params[:local] == false)
+ def reject(type, entity, local \\ true, additional \\ %{}) do
+ {:ok, entity, update_data} =
+ case type do
+ :join -> reject_join(entity, additional)
+ :follow -> reject_follow(entity, additional)
+ end
- with data <- %{
- "to" => to,
- "type" => "Reject",
- "actor" => actor,
- "object" => object,
- "id" => activity_wrapper_id || get_url(object) <> "/activity"
- },
- {:ok, activity} <- create_activity(data, local),
- {:ok, object} <- insert_full_object(data),
+ with {:ok, activity} <- create_activity(update_data, local),
:ok <- maybe_federate(activity) do
- {:ok, activity, object}
+ {:ok, activity, entity}
+ else
+ err ->
+ Logger.error("Something went wrong while creating an activity")
+ Logger.debug(inspect(err))
+ err
end
end
- # TODO: This is weird, maybe we shouldn't check here if we can make the activity.
- # def like(
- # %Actor{url: url} = actor,
- # object,
- # activity_id \\ nil,
- # local \\ true
- # ) do
- # with nil <- get_existing_like(url, object),
- # like_data <- make_like_data(user, object, activity_id),
- # {:ok, activity} <- create_activity(like_data, local),
- # {:ok, object} <- insert_full_object(data),
- # {:ok, object} <- add_like_to_object(activity, object),
- # :ok <- maybe_federate(activity) do
- # {:ok, activity, object}
- # else
- # %Activity{} = activity -> {:ok, activity, object}
- # error -> {:error, error}
- # end
- # end
-
- # def unlike(
- # %User{} = actor,
- # %Object{} = object,
- # activity_id \\ nil,
- # local \\ true
- # ) do
- # with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object),
- # unlike_data <- make_unlike_data(actor, like_activity, activity_id),
- # {:ok, unlike_activity} <- create_activity(unlike_data, local),
- # {:ok, _object} <- insert_full_object(data),
- # {:ok, _activity} <- Repo.delete(like_activity),
- # {:ok, object} <- remove_like_from_object(like_activity, object),
- # :ok <- maybe_federate(unlike_activity) do
- # {:ok, unlike_activity, like_activity, object}
- # else
- # _e -> {:ok, object}
- # end
- # end
-
def announce(
%Actor{} = actor,
object,
@@ -267,9 +244,10 @@ defmodule Mobilizon.Service.ActivityPub do
public \\ true
) do
with true <- is_public?(object),
+ {:ok, %Actor{id: object_owner_actor_id}} <- Actors.get_actor_by_url(object["actor"]),
+ {:ok, %Share{} = _share} <- Share.create(object["id"], actor.id, object_owner_actor_id),
announce_data <- make_announce_data(actor, object, activity_id, public),
{:ok, activity} <- create_activity(announce_data, local),
- {:ok, object} <- insert_full_object(announce_data),
:ok <- maybe_federate(activity) do
{:ok, activity, object}
else
@@ -288,7 +266,6 @@ defmodule Mobilizon.Service.ActivityPub do
with announce_activity <- make_announce_data(actor, object, cancelled_activity_id),
unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id),
{:ok, unannounce_activity} <- create_activity(unannounce_data, local),
- {:ok, object} <- insert_full_object(unannounce_data),
:ok <- maybe_federate(unannounce_activity) do
{:ok, unannounce_activity, object}
else
@@ -327,9 +304,8 @@ defmodule Mobilizon.Service.ActivityPub do
unfollow_data <-
make_unfollow_data(follower, followed, follow_activity, activity_unfollow_id),
{:ok, activity} <- create_activity(unfollow_data, local),
- {:ok, object} <- insert_full_object(unfollow_data),
:ok <- maybe_federate(activity) do
- {:ok, activity, object}
+ {:ok, activity, follow}
else
err ->
Logger.debug("Error while unfollowing an actor #{inspect(err)}")
@@ -339,6 +315,7 @@ defmodule Mobilizon.Service.ActivityPub do
def delete(object, local \\ true)
+ @spec delete(Event.t(), boolean) :: {:ok, Activity.t(), Event.t()}
def delete(%Event{url: url, organizer_actor: actor} = event, local) do
data = %{
"type" => "Delete",
@@ -348,15 +325,19 @@ defmodule Mobilizon.Service.ActivityPub do
"id" => url <> "/delete"
}
- with {:ok, %Event{} = event} <- Events.delete_event(event),
+ with audience <-
+ Audience.calculate_to_and_cc_from_mentions(event),
+ {:ok, %Event{} = event} <- Events.delete_event(event),
{:ok, %Tombstone{} = _tombstone} <-
Tombstone.create_tombstone(%{uri: event.url, actor_id: actor.id}),
- {:ok, activity} <- create_activity(data, local),
+ Share.delete_all_by_uri(event.url),
+ {:ok, activity} <- create_activity(Map.merge(data, audience), local),
:ok <- maybe_federate(activity) do
{:ok, activity, event}
end
end
+ @spec delete(Comment.t(), boolean) :: {:ok, Activity.t(), Comment.t()}
def delete(%Comment{url: url, actor: actor} = comment, local) do
data = %{
"type" => "Delete",
@@ -366,10 +347,13 @@ defmodule Mobilizon.Service.ActivityPub do
"to" => [actor.url <> "/followers", "https://www.w3.org/ns/activitystreams#Public"]
}
- with {:ok, %Comment{} = comment} <- Events.delete_comment(comment),
+ with audience <-
+ Audience.calculate_to_and_cc_from_mentions(comment),
+ {:ok, %Comment{} = comment} <- Events.delete_comment(comment),
{:ok, %Tombstone{} = _tombstone} <-
Tombstone.create_tombstone(%{uri: comment.url, actor_id: actor.id}),
- {:ok, activity} <- create_activity(data, local),
+ Share.delete_all_by_uri(comment.url),
+ {:ok, activity} <- create_activity(Map.merge(data, audience), local),
:ok <- maybe_federate(activity) do
{:ok, activity, comment}
end
@@ -384,7 +368,7 @@ defmodule Mobilizon.Service.ActivityPub do
"to" => [url <> "/followers", "https://www.w3.org/ns/activitystreams#Public"]
}
- with {:ok, %Actor{} = actor} <- Actors.delete_actor(actor),
+ with {:ok, %Oban.Job{}} <- Actors.delete_actor(actor),
{:ok, activity} <- create_activity(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity, actor}
@@ -396,6 +380,8 @@ defmodule Mobilizon.Service.ActivityPub do
{:create_report, {:ok, %Report{} = report}} <-
{:create_report, Reports.create_report(args)},
report_as_data <- Convertible.model_to_as(report),
+ cc <- if(local, do: [report.reported.url], else: []),
+ report_as_data <- Map.merge(report_as_data, %{"to" => [], "cc" => cc}),
{:ok, activity} <- create_activity(report_as_data, local),
:ok <- maybe_federate(activity) do
Enum.each(Users.list_moderators(), fn moderator ->
@@ -413,52 +399,56 @@ defmodule Mobilizon.Service.ActivityPub do
end
end
- def join(object, actor, local \\ true)
+ def join(object, actor, local \\ true, additional \\ %{})
- def join(%Event{options: options} = event, %Actor{} = actor, local) do
+ def join(%Event{} = event, %Actor{} = actor, local, additional) do
# TODO Refactor me for federation
- with maximum_attendee_capacity <-
- Map.get(options, :maximum_attendee_capacity) || 0,
- {:maximum_attendee_capacity, true} <-
- {:maximum_attendee_capacity,
- maximum_attendee_capacity == 0 ||
- Mobilizon.Events.count_participant_participants(event.id) <
- maximum_attendee_capacity},
- role <- Mobilizon.Events.get_default_participant_role(event),
+ with {:maximum_attendee_capacity, true} <-
+ {:maximum_attendee_capacity, check_attendee_capacity(event)},
{:ok, %Participant{} = participant} <-
Mobilizon.Events.create_participant(%{
- role: role,
+ role: :not_approved,
event_id: event.id,
- actor_id: actor.id
+ actor_id: actor.id,
+ url: Map.get(additional, :url)
}),
join_data <- Convertible.model_to_as(participant),
- join_data <- Map.put(join_data, "to", [event.organizer_actor.url]),
- join_data <- Map.put(join_data, "cc", []),
- {:ok, activity} <- create_activity(join_data, local),
- {:ok, _object} <- insert_full_object(join_data),
+ audience <-
+ Audience.calculate_to_and_cc_from_mentions(participant),
+ {:ok, activity} <- create_activity(Map.merge(join_data, audience), local),
:ok <- maybe_federate(activity) do
- if role === :participant do
- accept_join(
+ if event.local && Mobilizon.Events.get_default_participant_role(event) === :participant do
+ accept(
+ :join,
participant,
- %{}
+ true,
+ %{"actor" => event.organizer_actor.url}
)
+ else
+ {:ok, activity, participant}
end
-
- {:ok, activity, participant}
end
end
# TODO: Implement me
- def join(%Actor{type: :Group} = _group, %Actor{} = _actor, _local) do
+ def join(%Actor{type: :Group} = _group, %Actor{} = _actor, _local, _additional) do
:error
end
+ defp check_attendee_capacity(%Event{options: options} = event) do
+ with maximum_attendee_capacity <-
+ Map.get(options, :maximum_attendee_capacity) || 0 do
+ maximum_attendee_capacity == 0 ||
+ Mobilizon.Events.count_participant_participants(event.id) < maximum_attendee_capacity
+ end
+ end
+
def leave(object, actor, local \\ true)
# TODO: If we want to use this for exclusion we need to have an extra field
# for the actor that excluded the participant
def leave(
- %Event{id: event_id, url: event_url} = event,
+ %Event{id: event_id, url: event_url} = _event,
%Actor{id: actor_id, url: actor_url} = _actor,
local
) do
@@ -473,11 +463,11 @@ defmodule Mobilizon.Service.ActivityPub do
# If it's an exclusion it should be something else
"actor" => actor_url,
"object" => event_url,
- "to" => [event.organizer_actor.url],
- "cc" => []
+ "id" => "#{MobilizonWeb.Endpoint.url()}/leave/event/#{participant.id}"
},
- {:ok, activity} <- create_activity(leave_data, local),
- {:ok, _object} <- insert_full_object(leave_data),
+ audience <-
+ Audience.calculate_to_and_cc_from_mentions(participant),
+ {:ok, activity} <- create_activity(Map.merge(leave_data, audience), local),
:ok <- maybe_federate(activity) do
{:ok, activity, participant}
end
@@ -537,16 +527,22 @@ defmodule Mobilizon.Service.ActivityPub do
end
end
+ @spec is_create_activity?(Activity.t()) :: boolean
+ defp is_create_activity?(%Activity{data: %{"type" => "Create"}}), do: true
+ defp is_create_activity?(_), do: false
+
@doc """
Publish an activity to all appropriated audiences inboxes
"""
+ @spec publish(Actor.t(), Activity.t()) :: :ok
def publish(actor, activity) do
Logger.debug("Publishing an activity")
Logger.debug(inspect(activity))
public = is_public?(activity)
+ Logger.debug("is public ? #{public}")
- if public && !is_delete_activity?(activity) && Config.get([:instance, :allow_relay]) do
+ if public && is_create_activity?(activity) && Config.get([:instance, :allow_relay]) do
Logger.info(fn -> "Relaying #{activity.data["id"]} out" end)
Relay.publish(activity)
@@ -578,15 +574,12 @@ defmodule Mobilizon.Service.ActivityPub do
end)
end
- defp is_delete_activity?(%Activity{data: %{"type" => "Delete"}}), do: true
- defp is_delete_activity?(_), do: false
-
@doc """
Publish an activity to a specific inbox
"""
def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do
Logger.info("Federating #{id} to #{inbox}")
- %URI{host: host, path: _path} = URI.parse(inbox)
+ %URI{host: host, path: path} = URI.parse(inbox)
digest = Signature.build_digest(json)
date = Signature.generate_date_header()
@@ -594,10 +587,9 @@ defmodule Mobilizon.Service.ActivityPub do
signature =
Signature.sign(actor, %{
+ "(request-target)": "post #{path}",
host: host,
"content-length": byte_size(json),
- # TODO : Look me up in depth why Pleroma handles this inside lib/mobilizon_web/http_signature.ex
- # "(request-target)": request_target,
digest: digest,
date: date
})
@@ -627,7 +619,7 @@ defmodule Mobilizon.Service.ActivityPub do
:ok <- Logger.debug("response okay, now decoding json"),
{:ok, data} <- Jason.decode(body) do
Logger.debug("Got activity+json response at actor's endpoint, now converting data")
- actor_data_from_actor_object(data)
+ Mobilizon.Service.ActivityPub.Converter.Actor.as_to_model_data(data)
else
# Actor is gone, probably deleted
{:ok, %HTTPoison.Response{status_code: 410}} ->
@@ -642,49 +634,6 @@ defmodule Mobilizon.Service.ActivityPub do
res
end
- @doc """
- Creating proper actor data struct from AP data
-
-
- Convert ActivityPub data to our internal format
- """
- @spec actor_data_from_actor_object(map()) :: {:ok, map()}
- def actor_data_from_actor_object(data) when is_map(data) do
- avatar =
- data["icon"]["url"] &&
- %{
- "name" => data["icon"]["name"] || "avatar",
- "url" => data["icon"]["url"]
- }
-
- banner =
- data["image"]["url"] &&
- %{
- "name" => data["image"]["name"] || "banner",
- "url" => data["image"]["url"]
- }
-
- actor_data = %{
- url: data["id"],
- avatar: avatar,
- banner: banner,
- name: data["name"],
- preferred_username: data["preferredUsername"],
- summary: data["summary"],
- keys: data["publicKey"]["publicKeyPem"],
- inbox_url: data["inbox"],
- outbox_url: data["outbox"],
- following_url: data["following"],
- followers_url: data["followers"],
- shared_inbox_url: data["endpoints"]["sharedInbox"],
- domain: URI.parse(data["id"]).host,
- manually_approves_followers: data["manuallyApprovesFollowers"],
- type: data["type"]
- }
-
- {:ok, actor_data}
- end
-
@doc """
Return all public activities (events & comments) for an actor
"""
@@ -736,12 +685,7 @@ defmodule Mobilizon.Service.ActivityPub do
{:ok, %Event{} = event} <- Events.create_event(args),
event_as_data <- Convertible.model_to_as(event),
audience <-
- Audience.calculate_to_and_cc_from_mentions(
- event.organizer_actor,
- args.mentions,
- nil,
- event.visibility
- ),
+ Audience.calculate_to_and_cc_from_mentions(event),
create_data <-
make_create_data(event_as_data, Map.merge(audience, additional)) do
{:ok, event, create_data}
@@ -754,12 +698,7 @@ defmodule Mobilizon.Service.ActivityPub do
{:ok, %Comment{} = comment} <- Events.create_comment(args),
comment_as_data <- Convertible.model_to_as(comment),
audience <-
- Audience.calculate_to_and_cc_from_mentions(
- comment.actor,
- args.mentions,
- args.in_reply_to_comment,
- comment.visibility
- ),
+ Audience.calculate_to_and_cc_from_mentions(comment),
create_data <-
make_create_data(comment_as_data, Map.merge(audience, additional)) do
{:ok, comment, create_data}
@@ -771,13 +710,7 @@ defmodule Mobilizon.Service.ActivityPub do
with args <- prepare_args_for_group(args),
{:ok, %Actor{type: :Group} = group} <- Actors.create_group(args),
group_as_data <- Convertible.model_to_as(group),
- audience <-
- Audience.calculate_to_and_cc_from_mentions(
- args.creator_actor,
- [],
- nil,
- :public
- ),
+ audience <- %{"to" => ["https://www.w3.org/ns/activitystreams#Public"], "cc" => []},
create_data <-
make_create_data(group_as_data, Map.merge(audience, additional)) do
{:ok, group, create_data}
@@ -799,12 +732,7 @@ defmodule Mobilizon.Service.ActivityPub do
{:ok, %Event{} = new_event} <- Events.update_event(old_event, args),
event_as_data <- Convertible.model_to_as(new_event),
audience <-
- Audience.calculate_to_and_cc_from_mentions(
- new_event.organizer_actor,
- Map.get(args, :mentions, []),
- nil,
- new_event.visibility
- ),
+ Audience.calculate_to_and_cc_from_mentions(new_event),
update_data <- make_update_data(event_as_data, Map.merge(audience, additional)) do
{:ok, new_event, update_data}
else
@@ -821,34 +749,29 @@ defmodule Mobilizon.Service.ActivityPub do
with {:ok, %Actor{} = new_actor} <- Actors.update_actor(old_actor, args),
actor_as_data <- Convertible.model_to_as(new_actor),
audience <-
- Audience.calculate_to_and_cc_from_mentions(
- new_actor,
- [],
- nil,
- :public
- ),
+ Audience.calculate_to_and_cc_from_mentions(new_actor),
additional <- Map.merge(additional, %{"actor" => old_actor.url}),
update_data <- make_update_data(actor_as_data, Map.merge(audience, additional)) do
{:ok, new_actor, update_data}
end
end
- @spec accept_follow(Follower.t(), map(), map()) ::
+ @spec accept_follow(Follower.t(), map()) ::
{:ok, Follower.t(), Activity.t()} | any()
defp accept_follow(
%Follower{} = follower,
- args,
additional
) do
- with {:ok, %Follower{} = follower} <- Actors.update_follower(follower, args),
+ with {:ok, %Follower{} = follower} <- Actors.update_follower(follower, %{approved: true}),
follower_as_data <- Convertible.model_to_as(follower),
- audience <-
- Audience.calculate_to_and_cc_from_mentions(follower.target_actor),
update_data <-
- make_update_data(
+ make_accept_join_data(
follower_as_data,
- Map.merge(Map.merge(audience, additional), %{
- "id" => "#{MobilizonWeb.Endpoint.url()}/accept/follow/#{follower.id}"
+ Map.merge(additional, %{
+ "id" => "#{MobilizonWeb.Endpoint.url()}/accept/follow/#{follower.id}",
+ "to" => [follower.actor.url],
+ "cc" => [],
+ "actor" => follower.target_actor.url
})
) do
{:ok, follower, update_data}
@@ -860,17 +783,20 @@ defmodule Mobilizon.Service.ActivityPub do
end
end
- @spec accept_join(Participant.t(), map(), map()) ::
+ @spec accept_join(Participant.t(), map()) ::
{:ok, Participant.t(), Activity.t()} | any()
defp accept_join(
%Participant{} = participant,
- args,
- additional \\ %{}
+ additional
) do
- with {:ok, %Participant{} = participant} <- Events.update_participant(participant, args),
+ with {:ok, %Participant{} = participant} <-
+ Events.update_participant(participant, %{role: :participant}),
+ Absinthe.Subscription.publish(MobilizonWeb.Endpoint, participant.actor,
+ event_person_participation_changed: participant.actor.id
+ ),
participant_as_data <- Convertible.model_to_as(participant),
audience <-
- Audience.calculate_to_and_cc_from_mentions(participant.actor),
+ Audience.calculate_to_and_cc_from_mentions(participant),
update_data <-
make_accept_join_data(
participant_as_data,
@@ -887,6 +813,66 @@ defmodule Mobilizon.Service.ActivityPub do
end
end
+ @spec reject_join(Participant.t(), map()) ::
+ {:ok, Participant.t(), Activity.t()} | any()
+ defp reject_join(%Participant{} = participant, additional) do
+ with {:ok, %Participant{} = participant} <-
+ Events.update_participant(participant, %{approved: false, role: :rejected}),
+ Absinthe.Subscription.publish(MobilizonWeb.Endpoint, participant.actor,
+ event_person_participation_changed: participant.actor.id
+ ),
+ participant_as_data <- Convertible.model_to_as(participant),
+ audience <-
+ participant
+ |> Audience.calculate_to_and_cc_from_mentions()
+ |> Map.merge(additional),
+ reject_data <- %{
+ "type" => "Reject",
+ "object" => participant_as_data
+ },
+ update_data <-
+ reject_data
+ |> Map.merge(audience)
+ |> Map.merge(%{
+ "id" => "#{MobilizonWeb.Endpoint.url()}/reject/join/#{participant.id}"
+ }) do
+ {:ok, participant, update_data}
+ else
+ err ->
+ Logger.error("Something went wrong while creating an update activity")
+ Logger.debug(inspect(err))
+ err
+ end
+ end
+
+ @spec reject_follow(Follower.t(), map()) ::
+ {:ok, Follower.t(), Activity.t()} | any()
+ defp reject_follow(%Follower{} = follower, additional) do
+ with {:ok, %Follower{} = follower} <- Actors.delete_follower(follower),
+ follower_as_data <- Convertible.model_to_as(follower),
+ audience <-
+ follower.actor |> Audience.calculate_to_and_cc_from_mentions() |> Map.merge(additional),
+ reject_data <- %{
+ "to" => follower.actor.url,
+ "type" => "Reject",
+ "actor" => follower.actor.url,
+ "object" => follower_as_data
+ },
+ update_data <-
+ reject_data
+ |> Map.merge(audience)
+ |> Map.merge(%{
+ "id" => "#{MobilizonWeb.Endpoint.url()}/reject/follow/#{follower.id}"
+ }) do
+ {:ok, follower, update_data}
+ else
+ err ->
+ Logger.error("Something went wrong while creating an update activity")
+ Logger.debug(inspect(err))
+ err
+ end
+ end
+
# Prepare and sanitize arguments for events
defp prepare_args_for_event(args) do
# If title is not set: we are not updating it
@@ -923,7 +909,8 @@ defmodule Mobilizon.Service.ActivityPub do
# Prepare and sanitize arguments for comments
defp prepare_args_for_comment(args) do
with in_reply_to_comment <-
- args |> Map.get(:in_reply_to_comment_id) |> Events.get_comment(),
+ args |> Map.get(:in_reply_to_comment_id) |> Events.get_comment_with_preload(),
+ event <- args |> Map.get(:event_id) |> handle_event_for_comment(),
args <- Map.update(args, :visibility, :public, & &1),
{text, mentions, tags} <-
APIUtils.make_content_html(
@@ -940,6 +927,7 @@ defmodule Mobilizon.Service.ActivityPub do
text: text,
mentions: mentions,
tags: tags,
+ event: event,
in_reply_to_comment: in_reply_to_comment,
in_reply_to_comment_id:
if(is_nil(in_reply_to_comment), do: nil, else: Map.get(in_reply_to_comment, :id)),
@@ -953,6 +941,16 @@ defmodule Mobilizon.Service.ActivityPub do
end
end
+ @spec handle_event_for_comment(String.t() | integer() | nil) :: Event.t() | nil
+ defp handle_event_for_comment(event_id) when not is_nil(event_id) do
+ case Events.get_event_with_preload(event_id) do
+ {:ok, %Event{} = event} -> event
+ {:error, :event_not_found} -> nil
+ end
+ end
+
+ defp handle_event_for_comment(nil), do: nil
+
defp prepare_args_for_group(args) do
with preferred_username <-
args |> Map.get(:preferred_username) |> HtmlSanitizeEx.strip_tags() |> String.trim(),
diff --git a/lib/service/activity_pub/audience.ex b/lib/service/activity_pub/audience.ex
index 9f4c16cd3..cca7d1f66 100644
--- a/lib/service/activity_pub/audience.ex
+++ b/lib/service/activity_pub/audience.ex
@@ -2,7 +2,13 @@ defmodule Mobilizon.Service.ActivityPub.Audience do
@moduledoc """
Tools for calculating content audience
"""
+ alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
+ alias Mobilizon.Events.Comment
+ alias Mobilizon.Events.Event
+ alias Mobilizon.Events.Participant
+ alias Mobilizon.Share
+ require Logger
@ap_public "https://www.w3.org/ns/activitystreams#Public"
@@ -13,35 +19,27 @@ defmodule Mobilizon.Service.ActivityPub.Audience do
* `to` : the mentioned actors, the eventual actor we're replying to and the public
* `cc` : the actor's followers
"""
- @spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
- def get_to_and_cc(%Actor{} = actor, mentions, in_reply_to, :public) do
+ @spec get_to_and_cc(Actor.t(), list(), String.t()) :: {list(), list()}
+ def get_to_and_cc(%Actor{} = actor, mentions, :public) do
to = [@ap_public | mentions]
cc = [actor.followers_url]
- if in_reply_to do
- {Enum.uniq([in_reply_to.actor | to]), cc}
- else
- {to, cc}
- end
+ {to, cc}
end
@doc """
Determines the full audience based on mentions based on a unlisted audience
Audience is:
- * `to` : the mentionned actors, actor's followers and the eventual actor we're replying to
+ * `to` : the mentioned actors, actor's followers and the eventual actor we're replying to
* `cc` : public
"""
- @spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
- def get_to_and_cc(%Actor{} = actor, mentions, in_reply_to, :unlisted) do
+ @spec get_to_and_cc(Actor.t(), list(), String.t()) :: {list(), list()}
+ def get_to_and_cc(%Actor{} = actor, mentions, :unlisted) do
to = [actor.followers_url | mentions]
cc = [@ap_public]
- if in_reply_to do
- {Enum.uniq([in_reply_to.actor | to]), cc}
- else
- {to, cc}
- end
+ {to, cc}
end
@doc """
@@ -51,9 +49,9 @@ defmodule Mobilizon.Service.ActivityPub.Audience do
* `to` : the mentioned actors, actor's followers and the eventual actor we're replying to
* `cc` : none
"""
- @spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
- def get_to_and_cc(%Actor{} = actor, mentions, in_reply_to, :private) do
- {to, cc} = get_to_and_cc(actor, mentions, in_reply_to, :direct)
+ @spec get_to_and_cc(Actor.t(), list(), String.t()) :: {list(), list()}
+ def get_to_and_cc(%Actor{} = actor, mentions, :private) do
+ {to, cc} = get_to_and_cc(actor, mentions, :direct)
{[actor.followers_url | to], cc}
end
@@ -64,16 +62,12 @@ defmodule Mobilizon.Service.ActivityPub.Audience do
* `to` : the mentioned actors and the eventual actor we're replying to
* `cc` : none
"""
- @spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
- def get_to_and_cc(_actor, mentions, in_reply_to, :direct) do
- if in_reply_to do
- {Enum.uniq([in_reply_to.actor | mentions]), []}
- else
- {mentions, []}
- end
+ @spec get_to_and_cc(Actor.t(), list(), String.t()) :: {list(), list()}
+ def get_to_and_cc(_actor, mentions, :direct) do
+ {mentions, []}
end
- def get_to_and_cc(_actor, mentions, _in_reply_to, {:list, _}) do
+ def get_to_and_cc(_actor, mentions, {:list, _}) do
{mentions, []}
end
@@ -83,16 +77,109 @@ defmodule Mobilizon.Service.ActivityPub.Audience do
def get_addressed_actors(mentioned_users, _), do: mentioned_users
- def calculate_to_and_cc_from_mentions(
- actor,
- mentions \\ [],
- in_reply_to \\ nil,
- visibility \\ :public
- ) do
- with mentioned_actors <- for({_, mentioned_actor} <- mentions, do: mentioned_actor.url),
+ def calculate_to_and_cc_from_mentions(%Comment{} = comment) do
+ with mentioned_actors <- Enum.map(comment.mentions, &process_mention/1),
addressed_actors <- get_addressed_actors(mentioned_actors, nil),
- {to, cc} <- get_to_and_cc(actor, addressed_actors, in_reply_to, visibility) do
+ {to, cc} <- get_to_and_cc(comment.actor, addressed_actors, comment.visibility),
+ {to, cc} <- {Enum.uniq(to ++ add_in_reply_to(comment.in_reply_to_comment)), cc},
+ {to, cc} <- {Enum.uniq(to ++ add_event_author(comment.event)), cc},
+ {to, cc} <-
+ {to,
+ Enum.uniq(
+ cc ++
+ add_comments_authors([comment.origin_comment]) ++
+ add_shares_actors_followers(comment.url)
+ )} do
%{"to" => to, "cc" => cc}
end
end
+
+ def calculate_to_and_cc_from_mentions(%Event{} = event) do
+ with mentioned_actors <- Enum.map(event.mentions, &process_mention/1),
+ addressed_actors <- get_addressed_actors(mentioned_actors, nil),
+ {to, cc} <- get_to_and_cc(event.organizer_actor, addressed_actors, event.visibility),
+ {to, cc} <-
+ {to,
+ Enum.uniq(
+ cc ++ add_comments_authors(event.comments) ++ add_shares_actors_followers(event.url)
+ )} do
+ %{"to" => to, "cc" => cc}
+ end
+ end
+
+ def calculate_to_and_cc_from_mentions(%Participant{} = participant) do
+ participant = Mobilizon.Storage.Repo.preload(participant, [:actor, :event])
+
+ actor_participants_urls =
+ participant.event.id
+ |> Mobilizon.Events.list_actors_participants_for_event()
+ |> Enum.map(& &1.url)
+
+ %{"to" => [participant.actor.url], "cc" => actor_participants_urls}
+ end
+
+ def calculate_to_and_cc_from_mentions(%Actor{} = actor) do
+ %{
+ "to" => [@ap_public],
+ "cc" => [actor.followers_url] ++ add_actors_that_had_our_content(actor.id)
+ }
+ end
+
+ defp add_in_reply_to(%Comment{actor: %Actor{url: url}} = _comment), do: [url]
+ defp add_in_reply_to(%Event{organizer_actor: %Actor{url: url}} = _event), do: [url]
+ defp add_in_reply_to(_), do: []
+
+ defp add_event_author(nil), do: []
+
+ defp add_event_author(%Event{} = event) do
+ [Mobilizon.Storage.Repo.preload(event, [:organizer_actor]).organizer_actor.url]
+ end
+
+ defp add_comment_author(nil), do: nil
+
+ defp add_comment_author(%Comment{} = comment) do
+ case Mobilizon.Storage.Repo.preload(comment, [:actor]) do
+ %Comment{actor: %Actor{url: url}} ->
+ url
+
+ _err ->
+ nil
+ end
+ end
+
+ defp add_comments_authors(comments) do
+ authors =
+ comments
+ |> Enum.map(&add_comment_author/1)
+ |> Enum.filter(& &1)
+
+ authors
+ end
+
+ @spec add_shares_actors_followers(String.t()) :: list(String.t())
+ defp add_shares_actors_followers(uri) do
+ uri
+ |> Share.get_actors_by_share_uri()
+ |> Enum.map(&Actors.list_followers_actors_for_actor/1)
+ |> List.flatten()
+ |> Enum.map(& &1.url)
+ |> Enum.uniq()
+ end
+
+ defp add_actors_that_had_our_content(actor_id) do
+ actor_id
+ |> Share.get_actors_by_owner_actor_id()
+ |> Enum.map(&Actors.list_followers_actors_for_actor/1)
+ |> List.flatten()
+ |> Enum.map(& &1.url)
+ |> Enum.uniq()
+ end
+
+ defp process_mention({_, mentioned_actor}), do: mentioned_actor.url
+
+ defp process_mention(%{actor_id: actor_id}) do
+ with %Actor{url: url} <- Actors.get_actor(actor_id) do
+ url
+ end
+ end
end
diff --git a/lib/service/activity_pub/converter/actor.ex b/lib/service/activity_pub/converter/actor.ex
index 3a0c4546e..556a4d8b9 100644
--- a/lib/service/activity_pub/converter/actor.ex
+++ b/lib/service/activity_pub/converter/actor.ex
@@ -7,7 +7,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Actor do
"""
alias Mobilizon.Actors.Actor, as: ActorModel
- alias Mobilizon.Service.ActivityPub.{Converter, Convertible}
+ alias Mobilizon.Service.ActivityPub.{Converter, Convertible, Utils}
@behaviour Converter
@@ -22,33 +22,40 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Actor do
"""
@impl Converter
@spec as_to_model_data(map) :: map
- def as_to_model_data(object) do
+ def as_to_model_data(data) do
avatar =
- object["icon"]["url"] &&
+ data["icon"]["url"] &&
%{
- "name" => object["icon"]["name"] || "avatar",
- "url" => object["icon"]["url"]
+ "name" => data["icon"]["name"] || "avatar",
+ "url" => MobilizonWeb.MediaProxy.url(data["icon"]["url"])
}
banner =
- object["image"]["url"] &&
+ data["image"]["url"] &&
%{
- "name" => object["image"]["name"] || "banner",
- "url" => object["image"]["url"]
+ "name" => data["image"]["name"] || "banner",
+ "url" => MobilizonWeb.MediaProxy.url(data["image"]["url"])
}
- {:ok,
- %{
- "type" => String.to_existing_atom(object["type"]),
- "preferred_username" => object["preferredUsername"],
- "summary" => object["summary"],
- "url" => object["id"],
- "name" => object["name"],
- "avatar" => avatar,
- "banner" => banner,
- "keys" => object["publicKey"]["publicKeyPem"],
- "manually_approves_followers" => object["manuallyApprovesFollowers"]
- }}
+ actor_data = %{
+ url: data["id"],
+ avatar: avatar,
+ banner: banner,
+ name: data["name"],
+ preferred_username: data["preferredUsername"],
+ summary: data["summary"],
+ keys: data["publicKey"]["publicKeyPem"],
+ inbox_url: data["inbox"],
+ outbox_url: data["outbox"],
+ following_url: data["following"],
+ followers_url: data["followers"],
+ shared_inbox_url: data["endpoints"]["sharedInbox"],
+ domain: URI.parse(data["id"]).host,
+ manually_approves_followers: data["manuallyApprovesFollowers"],
+ type: data["type"]
+ }
+
+ {:ok, actor_data}
end
@doc """
@@ -57,18 +64,51 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Actor do
@impl Converter
@spec model_to_as(ActorModel.t()) :: map
def model_to_as(%ActorModel{} = actor) do
- %{
- "type" => Atom.to_string(actor.type),
- "to" => ["https://www.w3.org/ns/activitystreams#Public"],
- "preferred_username" => actor.preferred_username,
+ actor_data = %{
+ "id" => actor.url,
+ "type" => actor.type,
+ "preferredUsername" => actor.preferred_username,
"name" => actor.name,
"summary" => actor.summary,
- "following" => ActorModel.build_url(actor.preferred_username, :following),
- "followers" => ActorModel.build_url(actor.preferred_username, :followers),
- "inbox" => ActorModel.build_url(actor.preferred_username, :inbox),
- "outbox" => ActorModel.build_url(actor.preferred_username, :outbox),
- "id" => ActorModel.build_url(actor.preferred_username, :page),
- "url" => actor.url
+ "following" => actor.following_url,
+ "followers" => actor.followers_url,
+ "inbox" => actor.inbox_url,
+ "outbox" => actor.outbox_url,
+ "url" => actor.url,
+ "endpoints" => %{
+ "sharedInbox" => actor.shared_inbox_url
+ },
+ "manuallyApprovesFollowers" => actor.manually_approves_followers,
+ "publicKey" => %{
+ "id" => "#{actor.url}#main-key",
+ "owner" => actor.url,
+ "publicKeyPem" =>
+ if(is_nil(actor.domain) and not is_nil(actor.keys),
+ do: Utils.pem_to_public_key_pem(actor.keys),
+ else: actor.keys
+ )
+ }
}
+
+ actor_data =
+ if is_nil(actor.avatar) do
+ actor_data
+ else
+ Map.put(actor_data, "icon", %{
+ "type" => "Image",
+ "mediaType" => actor.avatar.content_type,
+ "url" => actor.avatar.url
+ })
+ end
+
+ if is_nil(actor.banner) do
+ actor_data
+ else
+ Map.put(actor_data, "image", %{
+ "type" => "Image",
+ "mediaType" => actor.banner.content_type,
+ "url" => actor.banner.url
+ })
+ end
end
end
diff --git a/lib/service/activity_pub/converter/comment.ex b/lib/service/activity_pub/converter/comment.ex
index da73e53c1..3875deb0a 100644
--- a/lib/service/activity_pub/converter/comment.ex
+++ b/lib/service/activity_pub/converter/comment.ex
@@ -12,6 +12,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do
alias Mobilizon.Service.ActivityPub
alias Mobilizon.Service.ActivityPub.{Converter, Convertible}
alias Mobilizon.Service.ActivityPub.Converter.Utils, as: ConverterUtils
+ alias Mobilizon.Tombstone, as: TombstoneModel
require Logger
@@ -32,9 +33,11 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do
Logger.debug("We're converting raw ActivityStream data to a comment entity")
Logger.debug(inspect(object))
- with {:ok, %Actor{id: actor_id}} <- ActivityPub.get_or_fetch_actor_by_url(object["actor"]),
- {:tags, tags} <- {:tags, ConverterUtils.fetch_tags(object["tag"])},
- {:mentions, mentions} <- {:mentions, ConverterUtils.fetch_mentions(object["tag"])} do
+ with author_url <- Map.get(object, "actor") || Map.get(object, "attributedTo"),
+ {:ok, %Actor{id: actor_id}} <- ActivityPub.get_or_fetch_actor_by_url(author_url),
+ {:tags, tags} <- {:tags, ConverterUtils.fetch_tags(Map.get(object, "tag", []))},
+ {:mentions, mentions} <-
+ {:mentions, ConverterUtils.fetch_mentions(Map.get(object, "tag", []))} do
Logger.debug("Inserting full comment")
Logger.debug(inspect(object))
@@ -70,6 +73,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do
data
|> Map.put(:in_reply_to_comment_id, id)
|> Map.put(:origin_comment_id, comment |> CommentModel.get_thread_id())
+ |> Map.put(:event_id, comment.event_id)
# Anything else is kind of a MP
{:error, parent} ->
@@ -106,6 +110,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do
"to" => to,
"cc" => [],
"content" => comment.text,
+ "mediaType" => "text/html",
"actor" => comment.actor.url,
"attributedTo" => comment.actor.url,
"uuid" => comment.uuid,
@@ -114,23 +119,27 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do
ConverterUtils.build_mentions(comment.mentions) ++ ConverterUtils.build_tags(comment.tags)
}
- if comment.in_reply_to_comment do
- object |> Map.put("inReplyTo", comment.in_reply_to_comment.url || comment.event.url)
- else
- object
+ cond do
+ comment.in_reply_to_comment ->
+ Map.put(object, "inReplyTo", comment.in_reply_to_comment.url)
+
+ comment.event ->
+ Map.put(object, "inReplyTo", comment.event.url)
+
+ true ->
+ object
end
end
@impl Converter
@spec model_to_as(CommentModel.t()) :: map
+ @doc """
+ A "soft-deleted" comment is a tombstone
+ """
def model_to_as(%CommentModel{} = comment) do
- %{
- "type" => "Tombstone",
- "uuid" => comment.uuid,
- "id" => comment.url,
- "published" => comment.inserted_at,
- "updated" => comment.updated_at,
- "deleted" => comment.deleted_at
- }
+ Convertible.model_to_as(%TombstoneModel{
+ uri: comment.url,
+ inserted_at: comment.deleted_at
+ })
end
end
diff --git a/lib/service/activity_pub/converter/event.ex b/lib/service/activity_pub/converter/event.ex
index e5c40e227..a273b87f1 100644
--- a/lib/service/activity_pub/converter/event.ex
+++ b/lib/service/activity_pub/converter/event.ex
@@ -6,14 +6,13 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
internal one, and back.
"""
- alias Mobilizon.{Addresses, Media}
+ alias Mobilizon.Addresses
alias Mobilizon.Actors.Actor
alias Mobilizon.Addresses.Address
alias Mobilizon.Events.Event, as: EventModel
- alias Mobilizon.Events.EventOptions
alias Mobilizon.Media.Picture
alias Mobilizon.Service.ActivityPub
- alias Mobilizon.Service.ActivityPub.{Converter, Convertible, Utils}
+ alias Mobilizon.Service.ActivityPub.{Converter, Convertible}
alias Mobilizon.Service.ActivityPub.Converter.Address, as: AddressConverter
alias Mobilizon.Service.ActivityPub.Converter.Picture, as: PictureConverter
alias Mobilizon.Service.ActivityPub.Converter.Utils, as: ConverterUtils
@@ -37,26 +36,25 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
Logger.debug("event as_to_model_data")
Logger.debug(inspect(object))
- with {:actor, {:ok, %Actor{id: actor_id}}} <-
- {:actor, ActivityPub.get_or_fetch_actor_by_url(object["actor"])},
+ with author_url <- Map.get(object, "actor") || Map.get(object, "attributedTo"),
+ {:actor, {:ok, %Actor{id: actor_id, domain: actor_domain}}} <-
+ {:actor, ActivityPub.get_or_fetch_actor_by_url(author_url)},
{:address, address_id} <-
{:address, get_address(object["location"])},
{:tags, tags} <- {:tags, ConverterUtils.fetch_tags(object["tag"])},
+ {:mentions, mentions} <- {:mentions, ConverterUtils.fetch_mentions(object["tag"])},
{:visibility, visibility} <- {:visibility, get_visibility(object)},
{:options, options} <- {:options, get_options(object)} do
picture_id =
with true <- Map.has_key?(object, "attachment") && length(object["attachment"]) > 0,
- %Picture{id: picture_id} <-
- Media.get_picture_by_url(
- object["attachment"]
- |> hd
- |> Map.get("url")
- |> hd
- |> Map.get("href")
- ) do
+ {:ok, %Picture{id: picture_id}} <-
+ object["attachment"]
+ |> hd
+ |> PictureConverter.find_or_create_picture(actor_id) do
picture_id
else
- _ -> nil
+ _err ->
+ nil
end
entity = %{
@@ -68,16 +66,20 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
ends_on: object["endTime"],
category: object["category"],
visibility: visibility,
- join_options: Map.get(object, "joinOptions", "free"),
+ join_options: Map.get(object, "joinMode", "free"),
+ local: is_nil(actor_domain),
options: options,
- status: object["status"],
+ status: object |> Map.get("ical:status", "CONFIRMED") |> String.downcase(),
online_address: object["onlineAddress"],
phone_address: object["phoneAddress"],
- draft: object["draft"] || false,
+ draft: false,
url: object["id"],
uuid: object["uuid"],
tags: tags,
- physical_address_id: address_id
+ mentions: mentions,
+ physical_address_id: address_id,
+ updated_at: object["updated"],
+ publish_at: object["published"]
}
{:ok, entity}
@@ -108,14 +110,17 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
"uuid" => event.uuid,
"category" => event.category,
"content" => event.description,
- "publish_at" => (event.publish_at || event.inserted_at) |> date_to_string(),
- "updated_at" => event.updated_at |> date_to_string(),
+ "published" => (event.publish_at || event.inserted_at) |> date_to_string(),
+ "updated" => event.updated_at |> date_to_string(),
"mediaType" => "text/html",
"startTime" => event.begins_on |> date_to_string(),
- "joinOptions" => to_string(event.join_options),
+ "joinMode" => to_string(event.join_options),
"endTime" => event.ends_on |> date_to_string(),
"tag" => event.tags |> ConverterUtils.build_tags(),
- "draft" => event.draft,
+ "maximumAttendeeCapacity" => event.options.maximum_attendee_capacity,
+ "repliesModerationOption" => event.options.comment_moderation,
+ # "draft" => event.draft,
+ "ical:status" => event.status |> to_string |> String.upcase(),
"id" => event.url,
"url" => event.url
}
@@ -133,17 +138,10 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
# Get only elements that we have in EventOptions
@spec get_options(map) :: map
defp get_options(object) do
- keys =
- EventOptions
- |> struct
- |> Map.keys()
- |> List.delete(:__struct__)
- |> Enum.map(&Utils.camelize/1)
-
- Enum.reduce(object, %{}, fn {key, value}, acc ->
- (!is_nil(value) && key in keys && Map.put(acc, Utils.underscore(key), value)) ||
- acc
- end)
+ %{
+ maximum_attendee_capacity: object["maximumAttendeeCapacity"],
+ comment_moderation: object["repliesModerationOption"]
+ }
end
@spec get_address(map | binary | nil) :: integer | nil
@@ -186,13 +184,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
@ap_public "https://www.w3.org/ns/activitystreams#Public"
- defp get_visibility(object) do
- cond do
- @ap_public in object["to"] -> :public
- @ap_public in object["cc"] -> :unlisted
- true -> :private
- end
- end
+ defp get_visibility(object), do: if(@ap_public in object["to"], do: :public, else: :unlisted)
@spec date_to_string(DateTime.t() | nil) :: String.t()
defp date_to_string(nil), do: nil
diff --git a/lib/service/activity_pub/converter/flag.ex b/lib/service/activity_pub/converter/flag.ex
index ac2c992b3..2980b0c69 100644
--- a/lib/service/activity_pub/converter/flag.ex
+++ b/lib/service/activity_pub/converter/flag.ex
@@ -15,6 +15,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Flag do
alias Mobilizon.Reports.Report
alias Mobilizon.Service.ActivityPub.Converter
alias Mobilizon.Service.ActivityPub.Convertible
+ alias Mobilizon.Service.ActivityPub.Relay
@behaviour Converter
@@ -42,8 +43,6 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Flag do
end
end
- @audience %{"to" => ["https://www.w3.org/ns/activitystreams#Public"], "cc" => []}
-
@doc """
Convert an event struct to an ActivityStream representation
"""
@@ -54,17 +53,13 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Flag do
object = if report.event, do: object ++ [report.event.url], else: object
- audience =
- if report.local, do: @audience, else: Map.put(@audience, "cc", [report.reported.url])
-
%{
"type" => "Flag",
- "actor" => report.reporter.url,
+ "actor" => Relay.get_actor().url,
"id" => report.url,
"content" => report.content,
"object" => object
}
- |> Map.merge(audience)
end
@spec as_to_model(map) :: map
@@ -91,7 +86,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Flag do
end
end),
- # Remove the reported user from the object list.
+ # Remove the reported actor and the event from the object list.
comments <-
Enum.filter(objects, fn url ->
!(url == reported.url || (!is_nil(event) && event.url == url))
diff --git a/lib/service/activity_pub/converter/picture.ex b/lib/service/activity_pub/converter/picture.ex
index 6c6e93ab6..292db9bda 100644
--- a/lib/service/activity_pub/converter/picture.ex
+++ b/lib/service/activity_pub/converter/picture.ex
@@ -15,14 +15,48 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Picture do
def model_to_as(%PictureModel{file: file}) do
%{
"type" => "Document",
- "url" => [
- %{
- "type" => "Link",
- "mediaType" => file.content_type,
- "href" => file.url
- }
- ],
+ "mediaType" => file.content_type,
+ "url" => file.url,
"name" => file.name
}
end
+
+ @doc """
+ Save picture data from raw data and return AS Link data.
+ """
+ def find_or_create_picture(%{"type" => "Link", "href" => url}, actor_id),
+ do: find_or_create_picture(url, actor_id)
+
+ def find_or_create_picture(
+ %{"type" => "Document", "url" => picture_url, "name" => name},
+ actor_id
+ )
+ when is_bitstring(picture_url) do
+ with {:ok, %HTTPoison.Response{body: body}} <- HTTPoison.get(picture_url),
+ {:ok,
+ %{
+ name: name,
+ url: url,
+ content_type: content_type,
+ size: size
+ }} <-
+ MobilizonWeb.Upload.store(%{body: body, name: name}),
+ {:picture_exists, nil} <- {:picture_exists, Mobilizon.Media.get_picture_by_url(url)} do
+ Mobilizon.Media.create_picture(%{
+ "file" => %{
+ "url" => url,
+ "name" => name,
+ "content_type" => content_type,
+ "size" => size
+ },
+ "actor_id" => actor_id
+ })
+ else
+ {:picture_exists, %PictureModel{file: _file} = picture} ->
+ {:ok, picture}
+
+ err ->
+ err
+ end
+ end
end
diff --git a/lib/service/activity_pub/converter/tombstone.ex b/lib/service/activity_pub/converter/tombstone.ex
new file mode 100644
index 000000000..8054ae376
--- /dev/null
+++ b/lib/service/activity_pub/converter/tombstone.ex
@@ -0,0 +1,40 @@
+defmodule Mobilizon.Service.ActivityPub.Converter.Tombstone do
+ @moduledoc """
+ Comment converter.
+
+ This module allows to convert Tombstone models to ActivityStreams data
+ """
+
+ alias Mobilizon.Tombstone, as: TombstoneModel
+ alias Mobilizon.Service.ActivityPub.{Converter, Convertible}
+
+ require Logger
+
+ @behaviour Converter
+
+ defimpl Convertible, for: TombstoneModel do
+ alias Mobilizon.Service.ActivityPub.Converter.Tombstone, as: TombstoneConverter
+
+ defdelegate model_to_as(comment), to: TombstoneConverter
+ end
+
+ @doc """
+ Make an AS tombstone object from an existing `Tombstone` structure.
+ """
+ @impl Converter
+ @spec model_to_as(TombstoneModel.t()) :: map
+ def model_to_as(%TombstoneModel{} = tombstone) do
+ %{
+ "type" => "Tombstone",
+ "id" => tombstone.uri,
+ "deleted" => tombstone.inserted_at
+ }
+ end
+
+ @doc """
+ Converting an Tombstone to an object makes no sense, nevertheless…
+ """
+ @impl Converter
+ @spec as_to_model_data(map) :: map
+ def as_to_model_data(object), do: object
+end
diff --git a/lib/service/activity_pub/converter/utils.ex b/lib/service/activity_pub/converter/utils.ex
index ba2f3a2af..8e637736f 100644
--- a/lib/service/activity_pub/converter/utils.ex
+++ b/lib/service/activity_pub/converter/utils.ex
@@ -14,6 +14,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Utils do
@spec fetch_tags([String.t()]) :: [Tag.t()]
def fetch_tags(tags) when is_list(tags) do
Logger.debug("fetching tags")
+ Logger.debug(inspect(tags))
tags |> Enum.flat_map(&fetch_tag/1) |> Enum.uniq() |> Enum.map(&existing_tag_or_data/1)
end
@@ -64,6 +65,8 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Utils do
}
end
+ defp fetch_tag(%{title: title}), do: [title]
+
defp fetch_tag(tag) when is_map(tag) do
case tag["type"] do
"Hashtag" ->
diff --git a/lib/service/activity_pub/relay.ex b/lib/service/activity_pub/relay.ex
index 90c2ab1ad..eb45e0d10 100644
--- a/lib/service/activity_pub/relay.ex
+++ b/lib/service/activity_pub/relay.ex
@@ -9,27 +9,37 @@ defmodule Mobilizon.Service.ActivityPub.Relay do
"""
alias Mobilizon.Actors
- alias Mobilizon.Actors.Actor
+ alias Mobilizon.Actors.{Actor, Follower}
alias Mobilizon.Service.ActivityPub
alias Mobilizon.Service.ActivityPub.{Activity, Transmogrifier}
+ alias Mobilizon.Service.WebFinger
alias MobilizonWeb.API.Follows
require Logger
+ def init() do
+ # Wait for everything to settle.
+ Process.sleep(1000 * 5)
+ get_actor()
+ end
+
+ @spec get_actor() :: Actor.t() | {:error, Ecto.Changeset.t()}
def get_actor do
with {:ok, %Actor{} = actor} <-
- Actors.get_or_create_actor_by_url("#{MobilizonWeb.Endpoint.url()}/relay") do
+ Actors.get_or_create_instance_actor_by_url("#{MobilizonWeb.Endpoint.url()}/relay") do
actor
end
end
- def follow(target_instance) do
- with %Actor{} = local_actor <- get_actor(),
+ @spec follow(String.t()) :: {:ok, Activity.t(), Follower.t()}
+ def follow(address) do
+ with {:ok, target_instance} <- fetch_actor(address),
+ %Actor{} = local_actor <- get_actor(),
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_actor_by_url(target_instance),
- {:ok, activity} <- Follows.follow(local_actor, target_actor) do
+ {:ok, activity, follow} <- Follows.follow(local_actor, target_actor) do
Logger.info("Relay: followed instance #{target_instance}; id=#{activity.data["id"]}")
- {:ok, activity}
+ {:ok, activity, follow}
else
e ->
Logger.warn("Error while following remote instance: #{inspect(e)}")
@@ -37,12 +47,14 @@ defmodule Mobilizon.Service.ActivityPub.Relay do
end
end
- def unfollow(target_instance) do
- with %Actor{} = local_actor <- get_actor(),
+ @spec unfollow(String.t()) :: {:ok, Activity.t(), Follower.t()}
+ def unfollow(address) do
+ with {:ok, target_instance} <- fetch_actor(address),
+ %Actor{} = local_actor <- get_actor(),
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_actor_by_url(target_instance),
- {:ok, activity} <- Follows.unfollow(local_actor, target_actor) do
+ {:ok, activity, follow} <- Follows.unfollow(local_actor, target_actor) do
Logger.info("Relay: unfollowed instance #{target_instance}: id=#{activity.data["id"]}")
- {:ok, activity}
+ {:ok, activity, follow}
else
e ->
Logger.warn("Error while unfollowing remote instance: #{inspect(e)}")
@@ -50,30 +62,38 @@ defmodule Mobilizon.Service.ActivityPub.Relay do
end
end
- def accept(target_instance) do
- with %Actor{} = local_actor <- get_actor(),
+ @spec accept(String.t()) :: {:ok, Activity.t(), Follower.t()}
+ def accept(address) do
+ Logger.debug("We're trying to accept a relay subscription")
+
+ with {:ok, target_instance} <- fetch_actor(address),
+ %Actor{} = local_actor <- get_actor(),
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_actor_by_url(target_instance),
- {:ok, activity} <- Follows.accept(target_actor, local_actor) do
- {:ok, activity}
+ {:ok, activity, follow} <- Follows.accept(target_actor, local_actor) do
+ {:ok, activity, follow}
end
end
- # def reject(target_instance) do
- # with %Actor{} = local_actor <- get_actor(),
- # {:ok, %Actor{} = target_actor} <- Activity.get_or_fetch_actor_by_url(target_instance),
- # {:ok, activity} <- Follows.reject(target_actor, local_actor) do
- # {:ok, activity}
- # end
- # end
+ def reject(address) do
+ Logger.debug("We're trying to reject a relay subscription")
+
+ with {:ok, target_instance} <- fetch_actor(address),
+ %Actor{} = local_actor <- get_actor(),
+ {:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_actor_by_url(target_instance),
+ {:ok, activity, follow} <- Follows.reject(target_actor, local_actor) do
+ {:ok, activity, follow}
+ end
+ end
@doc """
Publish an activity to all relays following this instance
"""
def publish(%Activity{data: %{"object" => object}} = _activity) do
with %Actor{id: actor_id} = actor <- get_actor(),
- {:ok, object} <-
- Transmogrifier.fetch_obj_helper_as_activity_streams(object) do
- ActivityPub.announce(actor, object, "#{object["id"]}/announces/#{actor_id}", true, false)
+ {object, object_id} <- fetch_object(object),
+ id <- "#{object_id}/announces/#{actor_id}" do
+ Logger.info("Publishing activity #{id} to all relays")
+ ActivityPub.announce(actor, object, id, true, false)
else
e ->
Logger.error("Error while getting local instance actor: #{inspect(e)}")
@@ -85,4 +105,51 @@ defmodule Mobilizon.Service.ActivityPub.Relay do
Logger.debug(inspect(err))
nil
end
+
+ defp fetch_object(object) when is_map(object) do
+ with {:ok, object} <- Transmogrifier.fetch_obj_helper_as_activity_streams(object) do
+ {object, object["id"]}
+ end
+ end
+
+ defp fetch_object(object) when is_bitstring(object), do: {object, object}
+
+ @spec fetch_actor(String.t()) :: {:ok, String.t()} | {:error, String.t()}
+ # Dirty hack
+ defp fetch_actor("https://" <> address), do: fetch_actor(address)
+ defp fetch_actor("http://" <> address), do: fetch_actor(address)
+
+ defp fetch_actor(address) do
+ %URI{host: host} = URI.parse("http://" <> address)
+
+ cond do
+ String.contains?(address, "@") ->
+ check_actor(address)
+
+ !is_nil(host) ->
+ check_actor("relay@#{host}")
+
+ true ->
+ {:error, "Bad URL"}
+ end
+ end
+
+ @spec check_actor(String.t()) :: {:ok, String.t()} | {:error, String.t()}
+ defp check_actor(username_and_domain) do
+ case Actors.get_actor_by_name(username_and_domain) do
+ %Actor{url: url} -> {:ok, url}
+ nil -> finger_actor(username_and_domain)
+ end
+ end
+
+ @spec finger_actor(String.t()) :: {:ok, String.t()} | {:error, String.t()}
+ defp finger_actor(nickname) do
+ case WebFinger.finger(nickname) do
+ {:ok, %{"url" => url}} when not is_nil(url) ->
+ {:ok, url}
+
+ _e ->
+ {:error, "No ActivityPub URL found in WebFinger"}
+ end
+ end
end
diff --git a/lib/service/activity_pub/transmogrifier.ex b/lib/service/activity_pub/transmogrifier.ex
index b82b3fbe2..d21d965da 100644
--- a/lib/service/activity_pub/transmogrifier.ex
+++ b/lib/service/activity_pub/transmogrifier.ex
@@ -20,108 +20,6 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
require Logger
- def get_actor(%{"actor" => actor}) when is_binary(actor) do
- actor
- end
-
- def get_actor(%{"actor" => actor}) when is_list(actor) do
- if is_binary(Enum.at(actor, 0)) do
- Enum.at(actor, 0)
- else
- actor
- |> Enum.find(fn %{"type" => type} -> type in ["Person", "Service", "Application"] end)
- |> Map.get("id")
- end
- end
-
- def get_actor(%{"actor" => %{"id" => id}}) when is_bitstring(id) do
- id
- end
-
- def get_actor(%{"actor" => nil, "attributedTo" => actor}) when not is_nil(actor) do
- get_actor(%{"actor" => actor})
- end
-
- @doc """
- Modifies an incoming AP object (mastodon format) to our internal format.
- """
- def fix_object(object) do
- object
- |> Map.put("actor", object["attributedTo"])
- |> fix_attachments
-
- # |> fix_in_reply_to
-
- # |> fix_tag
- end
-
- def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object)
- when not is_nil(in_reply_to) and is_bitstring(in_reply_to) do
- in_reply_to |> do_fix_in_reply_to(object)
- end
-
- def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object)
- when not is_nil(in_reply_to) and is_map(in_reply_to) do
- if is_bitstring(in_reply_to["id"]) do
- in_reply_to["id"] |> do_fix_in_reply_to(object)
- end
- end
-
- def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object)
- when not is_nil(in_reply_to) and is_list(in_reply_to) do
- if is_bitstring(Enum.at(in_reply_to, 0)) do
- in_reply_to |> Enum.at(0) |> do_fix_in_reply_to(object)
- end
- end
-
- def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object)
- when not is_nil(in_reply_to) do
- Logger.warn("inReplyTo ID seem incorrect: #{inspect(in_reply_to)}")
- do_fix_in_reply_to("", object)
- end
-
- def fix_in_reply_to(object), do: object
-
- def do_fix_in_reply_to(in_reply_to_id, object) do
- case fetch_obj_helper(in_reply_to_id) do
- {:ok, replied_object} ->
- object
- |> Map.put("inReplyTo", replied_object.url)
-
- {:error, {:error, :not_supported}} ->
- Logger.info("Object reply origin has not a supported type")
- object
-
- e ->
- Logger.warn("Couldn't fetch #{in_reply_to_id} #{inspect(e)}")
- object
- end
- end
-
- def fix_attachments(object) do
- attachments =
- (object["attachment"] || [])
- |> Enum.map(fn data ->
- url = [%{"type" => "Link", "mediaType" => data["mediaType"], "href" => data["url"]}]
- Map.put(data, "url", url)
- end)
-
- object
- |> Map.put("attachment", attachments)
- end
-
- def fix_tag(object) do
- tags =
- (object["tag"] || [])
- |> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end)
- |> Enum.map(fn data -> String.slice(data["name"], 1..-1) end)
-
- combined = (object["tag"] || []) ++ tags
-
- object
- |> Map.put("tag", combined)
- end
-
def handle_incoming(%{"id" => nil}), do: :error
def handle_incoming(%{"id" => ""}), do: :error
@@ -135,6 +33,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
additional: %{
"cc" => [params["reported"].url]
},
+ event_id: if(is_nil(params["event"]), do: nil, else: params["event"].id || nil),
local: false
}
@@ -158,7 +57,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
Logger.info("Handle incoming to create notes")
with {:ok, object_data} <-
- object |> fix_object() |> Converter.Comment.as_to_model_data(),
+ object |> Converter.Comment.as_to_model_data(),
{:existing_comment, {:error, :comment_not_found}} <-
{:existing_comment, Events.get_comment_from_url_with_preload(object_data.url)},
{:ok, %Activity{} = activity, %Comment{} = comment} <-
@@ -186,7 +85,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
Logger.info("Handle incoming to create event")
with {:ok, object_data} <-
- object |> fix_object() |> Converter.Event.as_to_model_data(),
+ object |> Converter.Event.as_to_model_data(),
{:existing_event, nil} <- {:existing_event, Events.get_event_by_url(object_data.url)},
{:ok, %Activity{} = activity, %Event{} = event} <-
ActivityPub.create(:event, object_data, false) do
@@ -273,36 +172,25 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
end
end
- #
- # def handle_incoming(
- # %{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = data
- # ) do
- # with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
- # {:ok, object} <-
- # fetch_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
- # {:ok, activity, object} <- ActivityPub.like(actor, object, id, false) do
- # {:ok, activity}
- # else
- # _e -> :error
- # end
- # end
- # #
def handle_incoming(
- %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => _id} = data
+ %{"type" => "Announce", "object" => object, "actor" => _actor, "id" => _id} = data
) do
with actor <- get_actor(data),
# TODO: Is the following line useful?
- {:ok, %Actor{} = _actor} <- ActivityPub.get_or_fetch_actor_by_url(actor),
+ {:ok, %Actor{id: actor_id} = _actor} <- ActivityPub.get_or_fetch_actor_by_url(actor),
:ok <- Logger.debug("Fetching contained object"),
- {:ok, object} <- fetch_obj_helper_as_activity_streams(object_id),
+ {:ok, object} <- fetch_obj_helper_as_activity_streams(object),
:ok <- Logger.debug("Handling contained object"),
create_data <-
make_create_data(object),
:ok <- Logger.debug(inspect(object)),
- {:ok, _activity, object} <- handle_incoming(create_data),
+ {:ok, _activity, entity} <- handle_incoming(create_data),
:ok <- Logger.debug("Finished processing contained object"),
- {:ok, activity} <- ActivityPub.create_activity(data, false) do
- {:ok, activity, object}
+ {:ok, activity} <- ActivityPub.create_activity(data, false),
+ {:ok, %Actor{id: object_owner_actor_id}} <- Actors.get_actor_by_url(object["actor"]),
+ {:ok, %Mobilizon.Share{} = _share} <-
+ Mobilizon.Share.create(object["id"], actor_id, object_owner_actor_id) do
+ {:ok, activity, entity}
else
e ->
Logger.debug(inspect(e))
@@ -318,7 +206,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
when object_type in ["Person", "Group", "Application", "Service", "Organization"] do
with {:ok, %Actor{} = old_actor} <- Actors.get_actor_by_url(object["id"]),
{:ok, object_data} <-
- object |> fix_object() |> Converter.Actor.as_to_model_data(),
+ object |> Converter.Actor.as_to_model_data(),
{:ok, %Activity{} = activity, %Actor{} = new_actor} <-
ActivityPub.update(:actor, old_actor, object_data, false) do
{:ok, activity, new_actor}
@@ -331,12 +219,15 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
def handle_incoming(
%{"type" => "Update", "object" => %{"type" => "Event"} = object, "actor" => _actor} =
- _update
+ update_data
) do
- with %Event{} = old_event <-
- Events.get_event_by_url(object["id"]),
+ with actor <- get_actor(update_data),
+ {:ok, %Actor{url: actor_url}} <- Actors.get_actor_by_url(actor),
+ {:ok, %Event{} = old_event} <-
+ object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(),
{:ok, object_data} <-
- object |> fix_object() |> Converter.Event.as_to_model_data(),
+ object |> Converter.Event.as_to_model_data(),
+ {:origin_check, true} <- {:origin_check, origin_check?(actor_url, update_data)},
{:ok, %Activity{} = activity, %Event{} = new_event} <-
ActivityPub.update(:event, old_event, object_data, false) do
{:ok, activity, new_event}
@@ -396,16 +287,18 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
def handle_incoming(
%{"type" => "Delete", "object" => object, "actor" => _actor, "id" => _id} = data
) do
- object_id = Utils.get_url(object)
-
with actor <- get_actor(data),
- {:ok, %Actor{url: _actor_url}} <- Actors.get_actor_by_url(actor),
- {:ok, object} <- fetch_obj_helper(object_id),
- # TODO : Validate that DELETE comes indeed form right domain (see above)
- # :ok <- contain_origin(actor_url, object.data),
+ {:ok, %Actor{url: actor_url}} <- Actors.get_actor_by_url(actor),
+ object_id <- Utils.get_url(object),
+ {:origin_check, true} <- {:origin_check, origin_check_from_id?(actor_url, object_id)},
+ {:ok, object} <- ActivityPub.fetch_object_from_url(object_id),
{:ok, activity, object} <- ActivityPub.delete(object, false) do
{:ok, activity, object}
else
+ {:origin_check, false} ->
+ Logger.warn("Object origin check failed")
+ :error
+
e ->
Logger.debug(inspect(e))
:error
@@ -413,12 +306,13 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Join", "object" => object, "actor" => _actor, "id" => _id} = data
+ %{"type" => "Join", "object" => object, "actor" => _actor, "id" => id} = data
) do
with actor <- get_actor(data),
{:ok, %Actor{url: _actor_url} = actor} <- Actors.get_actor_by_url(actor),
- {:ok, object} <- fetch_obj_helper(object),
- {:ok, activity, object} <- ActivityPub.join(object, actor, false) do
+ object <- Utils.get_url(object),
+ {:ok, object} <- ActivityPub.fetch_object_from_url(object),
+ {:ok, activity, object} <- ActivityPub.join(object, actor, false, %{url: id}) do
{:ok, activity, object}
else
e ->
@@ -432,7 +326,8 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
) do
with actor <- get_actor(data),
{:ok, %Actor{} = actor} <- Actors.get_actor_by_url(actor),
- {:ok, object} <- fetch_obj_helper(object),
+ object <- Utils.get_url(object),
+ {:ok, object} <- ActivityPub.fetch_object_from_url(object),
{:ok, activity, object} <- ActivityPub.leave(object, actor, false) do
{:ok, activity, object}
else
@@ -487,7 +382,6 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
ActivityPub.accept(
:follow,
follow,
- %{approved: true},
false
) do
{:ok, activity, follow}
@@ -511,23 +405,11 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
Handle incoming `Reject` activities wrapping a `Follow` activity
"""
def do_handle_incoming_reject_following(follow_object, %Actor{} = actor) do
- with {:follow,
- {:ok,
- %Follower{approved: false, actor: follower, id: follow_id, target_actor: followed} =
- follow}} <-
+ with {:follow, {:ok, %Follower{approved: false, target_actor: followed} = follow}} <-
{:follow, get_follow(follow_object)},
{:same_actor, true} <- {:same_actor, actor.id == followed.id},
{:ok, activity, _} <-
- ActivityPub.reject(
- %{
- to: [follower.url],
- actor: actor.url,
- object: follow_object,
- local: false
- },
- "#{MobilizonWeb.Endpoint.url()}/reject/follow/#{follow_id}"
- ),
- {:ok, %Follower{}} <- Actors.delete_follower(follow) do
+ ActivityPub.reject(:follow, follow) do
{:ok, activity, follow}
else
{:follow, _} ->
@@ -547,7 +429,8 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
# Handle incoming `Accept` activities wrapping a `Join` activity on an event
defp do_handle_incoming_accept_join(join_object, %Actor{} = actor_accepting) do
- with {:join_event, {:ok, %Participant{role: :not_approved, event: event} = participant}} <-
+ with {:join_event, {:ok, %Participant{role: role, event: event} = participant}}
+ when role in [:not_approved, :rejected] <-
{:join_event, get_participant(join_object)},
# TODO: The actor that accepts the Join activity may another one that the event organizer ?
# Or maybe for groups it's the group that sends the Accept activity
@@ -556,7 +439,6 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
ActivityPub.accept(
:join,
participant,
- %{role: :participant},
false
),
:ok <-
@@ -587,32 +469,20 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
# Handle incoming `Reject` activities wrapping a `Join` activity on an event
defp do_handle_incoming_reject_join(join_object, %Actor{} = actor_accepting) do
- with {:join_event,
- {:ok,
- %Participant{role: :not_approved, actor: actor, id: join_id, event: event} =
- participant}} <-
+ with {:join_event, {:ok, %Participant{event: event, role: role} = participant}}
+ when role != :rejected <-
{:join_event, get_participant(join_object)},
# TODO: The actor that accepts the Join activity may another one that the event organizer ?
# Or maybe for groups it's the group that sends the Accept activity
{:same_actor, true} <- {:same_actor, actor_accepting.id == event.organizer_actor_id},
- {:ok, activity, _} <-
- ActivityPub.reject(
- %{
- to: [actor.url],
- actor: actor_accepting.url,
- object: join_object,
- local: false
- },
- "#{MobilizonWeb.Endpoint.url()}/reject/join/#{join_id}"
- ),
- {:ok, %Participant{role: :rejected} = participant} <-
- Events.update_participant(participant, %{"role" => :rejected}),
+ {:ok, activity, participant} <-
+ ActivityPub.reject(:join, participant, false),
:ok <- Participation.send_emails_to_local_user(participant) do
{:ok, activity, participant}
else
- {:join_event, {:ok, %Participant{role: :participant}}} ->
- Logger.debug(
- "Tried to handle an Reject activity on a Join activity with a event object but the participant is already validated"
+ {:join_event, {:ok, %Participant{role: :rejected}}} ->
+ Logger.warn(
+ "Tried to handle an Reject activity on a Join activity with a event object but the participant is already rejected"
)
nil
@@ -662,49 +532,6 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
end
end
- def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) do
- with false <- String.starts_with?(in_reply_to, "http"),
- {:ok, replied_to_object} <- fetch_obj_helper(in_reply_to) do
- Map.put(object, "inReplyTo", replied_to_object["external_url"] || in_reply_to)
- else
- _e -> object
- end
- end
-
- def set_reply_to_uri(obj), do: obj
- #
- # # Prepares the object of an outgoing create activity.
- def prepare_object(object) do
- object
- # |> set_sensitive
- # |> add_hashtags
- |> add_mention_tags
- # |> add_emoji_tags
- |> add_attributed_to
- # |> prepare_attachments
- |> set_reply_to_uri
- end
-
- @doc """
- internal -> Mastodon
- """
- def prepare_outgoing(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
- Logger.debug("Prepare outgoing for a note creation")
-
- object =
- object
- |> prepare_object
-
- data =
- data
- |> Map.put("object", object)
- |> Map.merge(Utils.make_json_ld_header())
-
- Logger.debug("Finished prepare outgoing for a note creation")
-
- {:ok, data}
- end
-
def prepare_outgoing(%{"type" => _type} = data) do
data =
data
@@ -713,145 +540,6 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
{:ok, data}
end
- # def prepare_outgoing(%Event{} = event) do
- # event =
- # event
- # |> Map.from_struct()
- # |> Map.drop([:__meta__])
- # |> Map.put(:"@context", "https://www.w3.org/ns/activitystreams")
- # |> prepare_object
-
- # {:ok, event}
- # end
-
- # def prepare_outgoing(%Comment{} = comment) do
- # comment =
- # comment
- # |> Map.from_struct()
- # |> Map.drop([:__meta__])
- # |> Map.put(:"@context", "https://www.w3.org/ns/activitystreams")
- # |> prepare_object
-
- # {:ok, comment}
- # end
-
- #
- # def maybe_fix_object_url(data) do
- # if is_binary(data["object"]) and not String.starts_with?(data["object"], "http") do
- # case ActivityPub.fetch_object_from_id(data["object"]) do
- # {:ok, relative_object} ->
- # if relative_object.data["external_url"] do
- # data =
- # data
- # |> Map.put("object", relative_object.data["external_url"])
- # else
- # data
- # end
- #
- # e ->
- # Logger.error("Couldn't fetch #{data["object"]} #{inspect(e)}")
- # data
- # end
- # else
- # data
- # end
- # end
- #
-
- def add_hashtags(object) do
- tags =
- (object["tag"] || [])
- |> Enum.map(fn tag ->
- %{
- "href" => MobilizonWeb.Endpoint.url() <> "/tags/#{tag}",
- "name" => "##{tag}",
- "type" => "Hashtag"
- }
- end)
-
- object
- |> Map.put("tag", tags)
- end
-
- def add_mention_tags(object) do
- Logger.debug("add mention tags")
- Logger.debug(inspect(object))
-
- recipients =
- (object["to"] ++ (object["cc"] || [])) -- ["https://www.w3.org/ns/activitystreams#Public"]
-
- mentions =
- recipients
- |> Enum.filter(& &1)
- |> Enum.map(fn url ->
- case Actors.get_actor_by_url(url) do
- {:ok, actor} -> actor
- _ -> nil
- end
- end)
- |> Enum.filter(& &1)
- |> Enum.map(fn actor ->
- %{
- "type" => "Mention",
- "href" => actor.url,
- "name" => "@#{Actor.preferred_username_and_domain(actor)}"
- }
- end)
-
- tags = object["tag"] || []
-
- object
- |> Map.put("tag", tags ++ mentions)
- end
-
- #
- # # TODO: we should probably send mtime instead of unix epoch time for updated
- # def add_emoji_tags(object) do
- # tags = object["tag"] || []
- # emoji = object["emoji"] || []
- #
- # out =
- # emoji
- # |> Enum.map(fn {name, url} ->
- # %{
- # "icon" => %{"url" => url, "type" => "Image"},
- # "name" => ":" <> name <> ":",
- # "type" => "Emoji",
- # "updated" => "1970-01-01T00:00:00Z",
- # "id" => url
- # }
- # end)
- #
- # object
- # |> Map.put("tag", tags ++ out)
- # end
- #
-
- #
- # def set_sensitive(object) do
- # tags = object["tag"] || []
- # Map.put(object, "sensitive", "nsfw" in tags)
- # end
- #
- def add_attributed_to(object) do
- attributed_to = object["attributedTo"] || object["actor"]
-
- object |> Map.put("attributedTo", attributed_to)
- end
-
- #
- # def prepare_attachments(object) do
- # attachments =
- # (object["attachment"] || [])
- # |> Enum.map(fn data ->
- # [%{"mediaType" => media_type, "href" => href} | _] = data["url"]
- # %{"url" => href, "mediaType" => media_type, "name" => data["name"], "type" => "Document"}
- # end)
- #
- # object
- # |> Map.put("attachment", attachments)
- # end
-
@spec fetch_obj_helper(map() | String.t()) :: Event.t() | Comment.t() | Actor.t() | any()
def fetch_obj_helper(object) do
Logger.debug("fetch_obj_helper")
@@ -862,7 +550,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
{:ok, object}
err ->
- Logger.info("Error while fetching #{inspect(object)}")
+ Logger.warn("Error while fetching #{inspect(object)}")
{:error, err}
end
end
diff --git a/lib/service/activity_pub/utils.ex b/lib/service/activity_pub/utils.ex
index cc79aeb99..94c06a16a 100644
--- a/lib/service/activity_pub/utils.ex
+++ b/lib/service/activity_pub/utils.ex
@@ -8,20 +8,11 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
# Various ActivityPub related utils.
"""
- alias Ecto.Changeset
-
- alias Mobilizon.{Actors, Addresses, Events, Reports, Users}
+ alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
- alias Mobilizon.Addresses.Address
- alias Mobilizon.Events.{Comment, Event}
alias Mobilizon.Media.Picture
- alias Mobilizon.Reports.Report
alias Mobilizon.Service.ActivityPub.{Activity, Converter}
alias Mobilizon.Service.Federator
- alias Mobilizon.Storage.Repo
-
- alias MobilizonWeb.{Email, Endpoint}
- alias MobilizonWeb.Router.Helpers, as: Routes
require Logger
@@ -37,12 +28,31 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
%{
"@context" => [
"https://www.w3.org/ns/activitystreams",
- "https://litepub.github.io/litepub/context.jsonld",
+ "https://litepub.social/litepub/context.jsonld",
%{
"sc" => "http://schema.org#",
+ "ical" => "http://www.w3.org/2002/12/cal/ical#",
"Hashtag" => "as:Hashtag",
"category" => "sc:category",
- "uuid" => "sc:identifier"
+ "uuid" => "sc:identifier",
+ "maximumAttendeeCapacity" => "sc:maximumAttendeeCapacity",
+ "mz" => "https://joinmobilizon.org/ns#",
+ "repliesModerationOptionType" => %{
+ "@id" => "mz:repliesModerationOptionType",
+ "@type" => "rdfs:Class"
+ },
+ "repliesModerationOption" => %{
+ "@id" => "mz:repliesModerationOption",
+ "@type" => "mz:repliesModerationOptionType"
+ },
+ "joinModeType" => %{
+ "@id" => "mz:joinModeType",
+ "@type" => "rdfs:Class"
+ },
+ "joinMode" => %{
+ "@id" => "mz:joinMode",
+ "@type" => "mz:joinModeType"
+ }
}
]
}
@@ -112,128 +122,56 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
Map.put_new_lazy(map, "published", &make_date/0)
end
- @doc """
- Inserts a full object if it is contained in an activity.
- """
- def insert_full_object(object_data)
-
- @doc """
- Inserts a full object if it is contained in an activity.
- """
- def insert_full_object(%{"object" => %{"type" => "Event"} = object_data, "type" => "Create"})
- when is_map(object_data) do
- with {:ok, object_data} <-
- Converter.Event.as_to_model_data(object_data),
- {:ok, %Event{} = event} <- Events.create_event(object_data) do
- {:ok, event}
- end
+ def get_actor(%{"actor" => actor}) when is_binary(actor) do
+ actor
end
- def insert_full_object(%{"object" => %{"type" => "Group"} = object_data, "type" => "Create"})
- when is_map(object_data) do
- with object_data <-
- Map.put(object_data, "preferred_username", object_data["preferredUsername"]),
- {:ok, %Actor{} = group} <- Actors.create_group(object_data) do
- {:ok, group}
- end
- end
-
- @doc """
- Inserts a full object if it is contained in an activity.
- """
- def insert_full_object(%{"object" => %{"type" => "Note"} = object_data, "type" => "Create"})
- when is_map(object_data) do
- with data <- Converter.Comment.as_to_model_data(object_data),
- {:ok, %Comment{} = comment} <- Events.create_comment(data) do
- {:ok, comment}
+ def get_actor(%{"actor" => actor}) when is_list(actor) do
+ if is_binary(Enum.at(actor, 0)) do
+ Enum.at(actor, 0)
else
- err ->
- Logger.error("Error while inserting a remote comment inside database")
- Logger.debug(inspect(err))
- {:error, err}
+ actor
+ |> Enum.find(fn %{"type" => type} -> type in ["Person", "Service", "Application"] end)
+ |> Map.get("id")
end
end
+ def get_actor(%{"actor" => %{"id" => id}}) when is_bitstring(id) do
+ id
+ end
+
+ def get_actor(%{"actor" => nil, "attributedTo" => actor}) when not is_nil(actor) do
+ get_actor(%{"actor" => actor})
+ end
+
@doc """
- Inserts a full object if it is contained in an activity.
+ Checks that an incoming AP object's actor matches the domain it came from.
"""
- def insert_full_object(%{"type" => "Flag"} = object_data)
- when is_map(object_data) do
- with data <- Converter.Flag.as_to_model_data(object_data),
- {:ok, %Report{} = report} <- Reports.create_report(data) do
- Enum.each(Users.list_moderators(), fn moderator ->
- moderator
- |> Email.Admin.report(report)
- |> Email.Mailer.deliver_later()
- end)
+ def origin_check?(id, %{"actor" => actor} = params) when not is_nil(actor) do
+ id_uri = URI.parse(id)
+ actor_uri = URI.parse(get_actor(params))
- {:ok, report}
- else
- err ->
- Logger.error("Error while inserting report inside database")
- Logger.debug(inspect(err))
- {:error, err}
- end
+ compare_uris?(actor_uri, id_uri)
end
- def insert_full_object(_), do: {:ok, nil}
+ def origin_check?(_id, %{"actor" => nil}), do: false
- @doc """
- Update an object
- """
- @spec update_object(struct(), map()) :: {:ok, struct()} | any()
- def update_object(object, object_data)
+ def origin_check?(id, %{"attributedTo" => actor} = params),
+ do: origin_check?(id, Map.put(params, "actor", actor))
- def update_object(event_url, %{
- "object" => %{"type" => "Event"} = object_data,
- "type" => "Update"
- })
- when is_map(object_data) do
- with {:event_not_found, %Event{} = event} <-
- {:event_not_found, Events.get_event_by_url(event_url)},
- {:ok, object_data} <- Converter.Event.as_to_model_data(object_data),
- {:ok, %Event{} = event} <- Events.update_event(event, object_data) do
- {:ok, event}
- end
+ def origin_check?(_id, _data), do: false
+
+ defp compare_uris?(%URI{} = id_uri, %URI{} = other_uri), do: id_uri.host == other_uri.host
+
+ def origin_check_from_id?(id, other_id) when is_binary(other_id) do
+ id_uri = URI.parse(id)
+ other_uri = URI.parse(other_id)
+
+ compare_uris?(id_uri, other_uri)
end
- def update_object(actor_url, %{
- "object" => %{"type" => type_actor} = object_data,
- "type" => "Update"
- })
- when is_map(object_data) and type_actor in @actor_types do
- with {:ok, %Actor{} = actor} <- Actors.get_actor_by_url(actor_url),
- object_data <- Converter.Actor.as_to_model_data(object_data),
- {:ok, %Actor{} = actor} <- Actors.update_actor(actor, object_data) do
- {:ok, actor}
- end
- end
-
- def update_object(_, _), do: {:ok, nil}
-
- #### Like-related helpers
-
- # @doc """
- # Returns an existing like if a user already liked an object
- # """
- # def get_existing_like(actor, %{data: %{"id" => id}}) do
- # query =
- # from(
- # activity in Activity,
- # where: fragment("(?)->>'actor' = ?", activity.data, ^actor),
- # # this is to use the index
- # where:
- # fragment(
- # "coalesce((?)->'object'->>'id', (?)->>'object') = ?",
- # activity.data,
- # activity.data,
- # ^id
- # ),
- # where: fragment("(?)->>'type' = 'Like'", activity.data)
- # )
- #
- # Repo.one(query)
- # end
+ def origin_check_from_id?(id, %{"id" => other_id} = _params) when is_binary(other_id),
+ do: origin_check_from_id?(id, other_id)
@doc """
Save picture data from %Plug.Upload{} and return AS Link data.
@@ -284,255 +222,6 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
def make_picture_data(nil), do: nil
- @doc """
- Make an AP event object from an set of values
- """
- @spec make_event_data(
- String.t(),
- map(),
- String.t(),
- String.t(),
- map(),
- list(),
- map(),
- String.t()
- ) :: map()
- def make_event_data(
- actor,
- %{to: to, cc: cc} = _audience,
- title,
- content_html,
- picture \\ nil,
- tags \\ [],
- metadata \\ %{},
- uuid \\ nil,
- url \\ nil
- ) do
- Logger.debug("Making event data")
- uuid = uuid || Ecto.UUID.generate()
-
- res = %{
- "type" => "Event",
- "to" => to,
- "cc" => cc || [],
- "content" => content_html,
- "name" => title,
- "startTime" => metadata.begins_on,
- "endTime" => metadata.ends_on,
- "category" => metadata.category,
- "actor" => actor,
- "id" => url || Routes.page_url(Endpoint, :event, uuid),
- "joinOptions" => metadata.join_options,
- "status" => metadata.status,
- "onlineAddress" => metadata.online_address,
- "phoneAddress" => metadata.phone_address,
- "draft" => metadata.draft,
- "uuid" => uuid,
- "tag" =>
- tags |> Enum.uniq() |> Enum.map(fn tag -> %{"type" => "Hashtag", "name" => "##{tag}"} end)
- }
-
- res =
- if is_nil(metadata.physical_address),
- do: res,
- else: Map.put(res, "location", make_address_data(metadata.physical_address))
-
- res =
- if is_nil(picture), do: res, else: Map.put(res, "attachment", [make_picture_data(picture)])
-
- if is_nil(metadata.options) do
- res
- else
- options = Events.EventOptions |> struct(metadata.options) |> Map.from_struct()
-
- Enum.reduce(options, res, fn {key, value}, acc ->
- (!is_nil(value) && Map.put(acc, camelize(key), value)) ||
- acc
- end)
- end
- end
-
- def make_address_data(%Address{} = address) do
- # res = %{
- # "type" => "Place",
- # "name" => address.description,
- # "id" => address.url,
- # "address" => %{
- # "type" => "PostalAddress",
- # "streetAddress" => address.street,
- # "postalCode" => address.postal_code,
- # "addressLocality" => address.locality,
- # "addressRegion" => address.region,
- # "addressCountry" => address.country
- # }
- # }
- #
- # if is_nil(address.geom) do
- # res
- # else
- # Map.put(res, "geo", %{
- # "type" => "GeoCoordinates",
- # "latitude" => address.geom.coordinates |> elem(0),
- # "longitude" => address.geom.coordinates |> elem(1)
- # })
- # end
- address.url
- end
-
- def make_address_data(address) when is_map(address) do
- Address
- |> struct(address)
- |> make_address_data()
- end
-
- def make_address_data(address_url) when is_bitstring(address_url) do
- with %Address{} = address <- Addresses.get_address_by_url(address_url) do
- address.url
- end
- end
-
- @doc """
- Make an AP comment object from an set of values
- """
- def make_comment_data(
- actor,
- to,
- content_html,
- # attachments,
- inReplyTo \\ nil,
- tags \\ [],
- # _cw \\ nil,
- cc \\ []
- ) do
- Logger.debug("Making comment data")
- uuid = Ecto.UUID.generate()
-
- object = %{
- "type" => "Note",
- "to" => to,
- "cc" => cc,
- "content" => content_html,
- # "summary" => cw,
- # "attachment" => attachments,
- "actor" => actor,
- "id" => Routes.page_url(Endpoint, :comment, uuid),
- "uuid" => uuid,
- "tag" => tags |> Enum.uniq()
- }
-
- if inReplyTo do
- object
- |> Map.put("inReplyTo", inReplyTo)
- else
- object
- end
- end
-
- def make_group_data(
- actor,
- to,
- preferred_username,
- content_html,
- # attachments,
- tags \\ [],
- # _cw \\ nil,
- cc \\ []
- ) do
- uuid = Ecto.UUID.generate()
-
- %{
- "type" => "Group",
- "to" => to,
- "cc" => cc,
- "summary" => content_html,
- "attributedTo" => actor,
- "preferredUsername" => preferred_username,
- "id" => Actor.build_url(preferred_username, :page),
- "uuid" => uuid,
- "tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
- }
- end
-
- #### Like-related helpers
-
- @doc """
- Returns an existing like if a user already liked an object
- """
- # @spec get_existing_like(Actor.t, map()) :: nil
- # def get_existing_like(%Actor{url: url} = actor, %{data: %{"id" => id}}) do
- # nil
- # end
-
- # def make_like_data(%Actor{url: url} = actor, %{data: %{"id" => id}} = object, activity_id) do
- # data = %{
- # "type" => "Like",
- # "actor" => url,
- # "object" => id,
- # "to" => [actor.followers_url, object.data["actor"]],
- # "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
- # "context" => object.data["context"]
- # }
-
- # if activity_id, do: Map.put(data, "id", activity_id), else: data
- # end
-
- def update_element_in_object(property, element, object) do
- with new_data <-
- object.data
- |> Map.put("#{property}_count", length(element))
- |> Map.put("#{property}s", element),
- changeset <- Changeset.change(object, data: new_data),
- {:ok, object} <- Repo.update(changeset) do
- {:ok, object}
- end
- end
-
- # def update_likes_in_object(likes, object) do
- # update_element_in_object("like", likes, object)
- # end
- #
- # def add_like_to_object(%Activity{data: %{"actor" => actor}}, object) do
- # with likes <- [actor | object.data["likes"] || []] |> Enum.uniq() do
- # update_likes_in_object(likes, object)
- # end
- # end
- #
- # def remove_like_from_object(%Activity{data: %{"actor" => actor}}, object) do
- # with likes <- (object.data["likes"] || []) |> List.delete(actor) do
- # update_likes_in_object(likes, object)
- # end
- # end
-
- #### Follow-related helpers
-
- @doc """
- Makes a follow activity data for the given followed and follower
- """
- def make_follow_data(%Actor{url: followed_id}, %Actor{url: follower_id}, activity_id) do
- Logger.debug("Make follow data")
-
- data = %{
- "type" => "Follow",
- "actor" => follower_id,
- "to" => [followed_id],
- "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
- "object" => followed_id
- }
-
- data =
- if activity_id,
- do: Map.put(data, "id", activity_id),
- else: data
-
- Logger.debug(inspect(data))
-
- data
- end
-
- #### Announce-related helpers
-
- require Logger
-
@doc """
Make announce activity data for the given actor and object
"""
@@ -673,42 +362,6 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|> Map.merge(additional)
end
- #### Flag-related helpers
- @spec make_flag_data(map(), map()) :: map()
- def make_flag_data(params, additional) do
- object = [params.reported_actor_url] ++ params.comments_url
-
- object = if params[:event_url], do: object ++ [params.event_url], else: object
-
- %{
- "type" => "Flag",
- "id" => "#{MobilizonWeb.Endpoint.url()}/report/#{Ecto.UUID.generate()}",
- "actor" => params.reporter_url,
- "content" => params.content,
- "object" => object,
- "state" => "open"
- }
- |> Map.merge(additional)
- end
-
- def make_join_data(%Event{} = event, %Actor{} = actor) do
- %{
- "type" => "Join",
- "id" => "#{actor.url}/join/event/id",
- "actor" => actor.url,
- "object" => event.url
- }
- end
-
- def make_join_data(%Actor{type: :Group} = event, %Actor{} = actor) do
- %{
- "type" => "Join",
- "id" => "#{actor.url}/join/group/id",
- "actor" => actor.url,
- "object" => event.url
- }
- end
-
@doc """
Make accept join activity data
"""
@@ -718,7 +371,6 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
"type" => "Accept",
"to" => object["to"],
"cc" => object["cc"],
- "actor" => object["actor"],
"object" => object,
"id" => object["id"] <> "/activity"
}
@@ -741,37 +393,39 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
end
end
- @doc """
- Converts PEM encoded keys to a private key representation
- """
- def pem_to_private_key(pem) do
- [private_key_code] = :public_key.pem_decode(pem)
- :public_key.pem_entry_decode(private_key_code)
- end
-
- @doc """
- Converts PEM encoded keys to a PEM public key representation
- """
def pem_to_public_key_pem(pem) do
public_key = pem_to_public_key(pem)
public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key)
:public_key.pem_encode([public_key])
end
- def camelize(word) when is_atom(word) do
- camelize(to_string(word))
+ defp make_signature(id, date) do
+ uri = URI.parse(id)
+
+ signature =
+ Mobilizon.Service.ActivityPub.Relay.get_actor()
+ |> Mobilizon.Service.HTTPSignatures.Signature.sign(%{
+ "(request-target)": "get #{uri.path}",
+ host: uri.host,
+ date: date
+ })
+
+ [{:Signature, signature}]
end
- def camelize(word) when is_bitstring(word) do
- {first, rest} = String.split_at(Macro.camelize(word), 1)
- String.downcase(first) <> rest
+ def sign_fetch(headers, id, date) do
+ if Mobilizon.Config.get([:activitypub, :sign_object_fetches]) do
+ headers ++ make_signature(id, date)
+ else
+ headers
+ end
end
- def underscore(word) when is_atom(word) do
- underscore(to_string(word))
- end
-
- def underscore(word) when is_bitstring(word) do
- Macro.underscore(word)
+ def maybe_date_fetch(headers, date) do
+ if Mobilizon.Config.get([:activitypub, :sign_object_fetches]) do
+ headers ++ [{:Date, date}]
+ else
+ headers
+ end
end
end
diff --git a/lib/service/activity_pub/visibility.ex b/lib/service/activity_pub/visibility.ex
index 018aacb63..fa89c6ec9 100644
--- a/lib/service/activity_pub/visibility.ex
+++ b/lib/service/activity_pub/visibility.ex
@@ -17,7 +17,10 @@ defmodule Mobilizon.Service.ActivityPub.Visibility do
def is_public?(%{data: %{"type" => "Tombstone"}}), do: false
def is_public?(%{data: data}), do: is_public?(data)
def is_public?(%Activity{data: data}), do: is_public?(data)
- def is_public?(data) when is_map(data), do: @public in (data["to"] ++ (data["cc"] || []))
+
+ def is_public?(data) when is_map(data),
+ do: @public in (Map.get(data, "to", []) ++ Map.get(data, "cc", []))
+
def is_public?(%Comment{deleted_at: deleted_at}), do: !is_nil(deleted_at)
def is_public?(err), do: raise(ArgumentError, message: "Invalid argument #{inspect(err)}")
end
diff --git a/lib/service/formatter/formatter.ex b/lib/service/formatter/formatter.ex
index 282fc392f..33924d68c 100644
--- a/lib/service/formatter/formatter.ex
+++ b/lib/service/formatter/formatter.ex
@@ -34,16 +34,14 @@ defmodule Mobilizon.Service.Formatter do
def mention_handler("@" <> nickname, buffer, _opts, acc) do
case Actors.get_actor_by_name(nickname) do
- %Actor{preferred_username: preferred_username} = actor ->
- link = "@#{preferred_username}"
+ # %Actor{preferred_username: preferred_username} = actor ->
+ # link = "@#{preferred_username}"
+ #
+ # {link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, actor})}}
- {link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, actor})}}
-
- %Actor{type: :Person, id: id, url: url, preferred_username: preferred_username} = actor ->
+ %Actor{type: :Person, id: id, preferred_username: preferred_username} = actor ->
link =
- "@#{
- preferred_username
- }"
+ "@#{preferred_username}"
{link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, actor})}}
diff --git a/lib/service/html.ex b/lib/service/html.ex
index a30af219f..02c4d88c3 100644
--- a/lib/service/html.ex
+++ b/lib/service/html.ex
@@ -38,7 +38,8 @@ defmodule Mobilizon.Service.HTML.Scrubber.Default do
"tag",
"nofollow",
"noopener",
- "noreferrer"
+ "noreferrer",
+ "ugc"
])
Meta.allow_tag_with_these_attributes("a", ["name", "title"])
@@ -61,8 +62,8 @@ defmodule Mobilizon.Service.HTML.Scrubber.Default do
Meta.allow_tag_with_these_attributes("ul", [])
Meta.allow_tag_with_these_attributes("img", ["src", "alt"])
- Meta.allow_tag_with_this_attribute_values("span", "class", ["h-card"])
- Meta.allow_tag_with_these_attributes("span", [])
+ Meta.allow_tag_with_this_attribute_values("span", "class", ["h-card", "mention"])
+ Meta.allow_tag_with_these_attributes("span", ["data-user"])
Meta.allow_tag_with_these_attributes("h1", [])
Meta.allow_tag_with_these_attributes("h2", [])
diff --git a/lib/service/http_signatures/signature.ex b/lib/service/http_signatures/signature.ex
index a4eb51ea2..df929617f 100644
--- a/lib/service/http_signatures/signature.ex
+++ b/lib/service/http_signatures/signature.ex
@@ -15,6 +15,7 @@ defmodule Mobilizon.Service.HTTPSignatures.Signature do
require Logger
+ @spec key_id_to_actor_url(String.t()) :: String.t()
def key_id_to_actor_url(key_id) do
%{path: path} =
uri =
@@ -46,12 +47,10 @@ defmodule Mobilizon.Service.HTTPSignatures.Signature do
end
end
- @doc """
- Gets a public key for a given ActivityPub actor ID (url).
- """
+ # Gets a public key for a given ActivityPub actor ID (url).
@spec get_public_key_for_url(String.t()) ::
{:ok, String.t()} | {:error, :actor_fetch_error | :pem_decode_error}
- def get_public_key_for_url(url) do
+ defp get_public_key_for_url(url) do
with {:ok, %Actor{keys: keys}} <- ActivityPub.get_or_fetch_actor_by_url(url),
{:ok, public_key} <- prepare_public_key(keys) do
{:ok, public_key}
@@ -103,16 +102,10 @@ defmodule Mobilizon.Service.HTTPSignatures.Signature do
end
end
- def generate_date_header(date \\ Timex.now("GMT")) do
- case Timex.format(date, "%a, %d %b %Y %H:%M:%S %Z", :strftime) do
- {:ok, date} ->
- date
+ def generate_date_header, do: generate_date_header(NaiveDateTime.utc_now())
- {:error, err} ->
- Logger.error("Unable to generate date header")
- Logger.debug(inspect(err))
- nil
- end
+ def generate_date_header(%NaiveDateTime{} = date) do
+ Timex.format!(date, "{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
end
def generate_request_target(method, path), do: "#{method} #{path}"
diff --git a/lib/service/workers/background_worker.ex b/lib/service/workers/background_worker.ex
new file mode 100644
index 000000000..60cbe2122
--- /dev/null
+++ b/lib/service/workers/background_worker.ex
@@ -0,0 +1,17 @@
+defmodule Mobilizon.Service.Workers.BackgroundWorker do
+ @moduledoc """
+ Worker to build search results
+ """
+
+ alias Mobilizon.Actors
+ alias Mobilizon.Actors.Actor
+
+ use Mobilizon.Service.Workers.WorkerHelper, queue: "background"
+
+ @impl Oban.Worker
+ def perform(%{"op" => "delete_actor", "actor_id" => actor_id}, _job) do
+ with %Actor{} = actor <- Actors.get_actor(actor_id) do
+ Actors.perform(:delete_actor, actor)
+ end
+ end
+end
diff --git a/mix.exs b/mix.exs
index d6612bb26..1591f74bb 100644
--- a/mix.exs
+++ b/mix.exs
@@ -60,6 +60,7 @@ defmodule Mobilizon.Mixfile do
{:cowboy, "~> 2.6"},
{:guardian, "~> 2.0"},
{:guardian_db, "~> 2.0.2"},
+ {:guardian_phoenix, "~> 2.0"},
{:argon2_elixir, "~> 2.0"},
{:cors_plug, "~> 2.0"},
{:ecto_autoslug_field, "~> 2.0"},
diff --git a/mix.lock b/mix.lock
index 09d4aef61..650275303 100644
--- a/mix.lock
+++ b/mix.lock
@@ -60,6 +60,7 @@
"gettext": {:hex, :gettext, "0.17.1", "8baab33482df4907b3eae22f719da492cee3981a26e649b9c2be1c0192616962", [:mix], [], "hexpm"},
"guardian": {:hex, :guardian, "2.0.0", "5d3e537832b7cf35c8674da92457b7be671666a2eff4bf0f2ccfcfb3a8c67a0b", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"},
"guardian_db": {:hex, :guardian_db, "2.0.2", "6247303fda5ed90e19ea1d2e4c5a65b13f58cc12810f95f71b6ffb50ef2d057f", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.1.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:guardian, "~> 1.0 or ~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"},
+ "guardian_phoenix": {:hex, :guardian_phoenix, "2.0.1", "89a817265af09a6ddf7cb1e77f17ffca90cea2db10ff888375ef34502b2731b1", [:mix], [{:guardian, "~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"},
"hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
"http_sign": {:hex, :http_sign, "0.1.1", "b16edb83aa282892f3271f9a048c155e772bf36e15700ab93901484c55f8dd10", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
diff --git a/mkdocs.yml b/mkdocs.yml
index f72f58f39..1089c44d9 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -20,7 +20,7 @@ markdown_extensions:
- pymdownx.mark
plugins:
- search
- - git-revision-date
+ - git-revision-date-localized
theme:
name: 'material'
custom_dir: 'docs/theme/'
diff --git a/priv/repo/migrations/20191129091227_add_timestamps_to_followers.exs b/priv/repo/migrations/20191129091227_add_timestamps_to_followers.exs
new file mode 100644
index 000000000..dc582b82e
--- /dev/null
+++ b/priv/repo/migrations/20191129091227_add_timestamps_to_followers.exs
@@ -0,0 +1,9 @@
+defmodule Mobilizon.Storage.Repo.Migrations.AddTimestampsToFollowers do
+ use Ecto.Migration
+
+ def change do
+ alter table(:followers) do
+ timestamps()
+ end
+ end
+end
diff --git a/priv/repo/migrations/20191204164224_delete_event_cascade_to_comments.exs b/priv/repo/migrations/20191204164224_delete_event_cascade_to_comments.exs
new file mode 100644
index 000000000..9d725dd81
--- /dev/null
+++ b/priv/repo/migrations/20191204164224_delete_event_cascade_to_comments.exs
@@ -0,0 +1,19 @@
+defmodule Mobilizon.Storage.Repo.Migrations.DeleteEventCascadeToComments do
+ use Ecto.Migration
+
+ def up do
+ drop_if_exists(constraint(:comments, "comments_event_id_fkey"))
+
+ alter table(:comments) do
+ modify(:event_id, references(:events, on_delete: :delete_all))
+ end
+ end
+
+ def down do
+ drop_if_exists(constraint(:comments, "comments_event_id_fkey"))
+
+ alter table(:comments) do
+ modify(:event_id, references(:events, on_delete: :nothing))
+ end
+ end
+end
diff --git a/priv/repo/migrations/20191206144028_create_shares.exs b/priv/repo/migrations/20191206144028_create_shares.exs
new file mode 100644
index 000000000..ccfce195c
--- /dev/null
+++ b/priv/repo/migrations/20191206144028_create_shares.exs
@@ -0,0 +1,23 @@
+defmodule Mobilizon.Repo.Migrations.CreateShares do
+ use Ecto.Migration
+
+ def up do
+ create table(:shares) do
+ add(:uri, :string, null: false)
+ add(:actor_id, references(:actors, on_delete: :delete_all), null: false)
+ add(:owner_actor_id, references(:actors, on_delete: :delete_all), null: false)
+
+ timestamps()
+ end
+
+ create_if_not_exists(
+ index(:shares, [:uri, :actor_id], unique: true, name: :shares_uri_actor_id_index)
+ )
+ end
+
+ def down do
+ drop_if_exists(index(:shares, [:uri, :actor_id]))
+
+ drop_if_exists(table(:shares))
+ end
+end
diff --git a/schema.graphql b/schema.graphql
index 66a412b0b..07dc7f638 100644
--- a/schema.graphql
+++ b/schema.graphql
@@ -1,9 +1,10 @@
# source: http://localhost:4000/api
-# timestamp: Fri Nov 22 2019 18:34:33 GMT+0100 (Central European Standard Time)
+# timestamp: Wed Dec 11 2019 15:24:29 GMT+0100 (heure normale d’Europe centrale)
schema {
query: RootQueryType
mutation: RootMutationType
+ subscription: RootSubscriptionType
}
"""An action log"""
@@ -25,6 +26,7 @@ type ActionLog {
}
enum ActionLogAction {
+ COMMENT_DELETION
EVENT_DELETION
EVENT_UPDATE
NOTE_CREATION
@@ -66,9 +68,6 @@ interface Actor {
"""Internal ID for this actor"""
id: ID
- """The actors RSA Keys"""
- keys: String
-
"""If the actor is from this instance"""
local: Boolean
@@ -78,9 +77,6 @@ interface Actor {
"""The actor's displayed name"""
name: String
- """A list of the events this actor has organized"""
- organizedEvents: [Event]
-
"""The actor's preferred username"""
preferredUsername: String
@@ -155,8 +151,62 @@ input AddressInput {
url: String
}
+"""
+Represents an application
+
+"""
+type Application implements Actor {
+ """The actor's avatar picture"""
+ avatar: Picture
+
+ """The actor's banner picture"""
+ banner: Picture
+
+ """The actor's domain if (null if it's this instance)"""
+ domain: String
+
+ """List of followers"""
+ followers: [Follower]
+
+ """Number of followers for this actor"""
+ followersCount: Int
+
+ """List of followings"""
+ following: [Follower]
+
+ """Number of actors following this actor"""
+ followingCount: Int
+
+ """Internal ID for this application"""
+ id: ID
+
+ """If the actor is from this instance"""
+ local: Boolean
+
+ """Whether the actors manually approves followers"""
+ manuallyApprovesFollowers: Boolean
+
+ """The actor's displayed name"""
+ name: String
+
+ """The actor's preferred username"""
+ preferredUsername: String
+
+ """The actor's summary"""
+ summary: String
+
+ """If the actor is suspended"""
+ suspended: Boolean
+
+ """The type of Actor (Person, Group,…)"""
+ type: ActorType
+
+ """The ActivityPub actor's URL"""
+ url: String
+}
+
"""A comment"""
-type Comment {
+type Comment implements ActionLogObject {
actor: Person
deletedAt: DateTime
event: Event
@@ -542,8 +592,14 @@ type Follower {
"""Whether the follow has been approved by the target actor"""
approved: Boolean
+ """When the follow was created"""
+ insertedAt: DateTime
+
"""What or who the profile follows"""
targetActor: Actor
+
+ """When the follow was updated"""
+ updatedAt: DateTime
}
type Geocoding {
@@ -580,9 +636,6 @@ type Group implements Actor {
"""Internal ID for this group"""
id: ID
- """The actors RSA Keys"""
- keys: String
-
"""If the actor is from this instance"""
local: Boolean
@@ -693,6 +746,14 @@ enum Openness {
OPEN
}
+type PaginatedFollowerList {
+ """A list of followers"""
+ elements: [Follower]
+
+ """The total number of elements in the list"""
+ total: Int
+}
+
"""Represents a participant to an event"""
type Participant {
"""The actor that participates to the event"""
@@ -772,9 +833,6 @@ type Person implements Actor {
"""Internal ID for this person"""
id: ID
- """The actors RSA Keys"""
- keys: String
-
"""If the actor is from this instance"""
local: Boolean
@@ -857,7 +915,7 @@ input PictureInputObject {
}
"""
-The `Point` scalar type represents Point geographic information compliant string data,
+The `Point` scalar type represents Point geographic information compliant string data,
represented as floats separated by a semi-colon. The geodetic system is WGS 84
"""
scalar Point
@@ -941,11 +999,66 @@ type RootMutationType {
"""Change default actor for user"""
changeDefaultActor(preferredUsername: String!): User
- """Change an user password"""
- changePassword(newPassword: String!, oldPassword: String!): User
+ """Create a new person for user"""
+ createPerson(
+ """
+ The avatar for the profile, either as an object or directly the ID of an existing Picture
+ """
+ avatar: PictureInput
- """Create a comment"""
- createComment(actorId: ID!, eventId: ID, inReplyToCommentId: ID, text: String!): Comment
+ """
+ The banner for the profile, either as an object or directly the ID of an existing Picture
+ """
+ banner: PictureInput
+
+ """The displayed name for the new profile"""
+ name: String = ""
+ preferredUsername: String!
+
+ """The summary for the new profile"""
+ summary: String = ""
+ ): Person
+
+ """Upload a picture"""
+ uploadPicture(actorId: ID!, alt: String, file: Upload!, name: String!): Picture
+
+ """Delete an event"""
+ deleteEvent(actorId: ID!, eventId: ID!): DeletedObject
+
+ """Create a note on a report"""
+ createReportNote(content: String, moderatorId: ID!, reportId: ID!): ReportNote
+
+ """Accept a relay subscription"""
+ acceptRelay(address: String!): Follower
+
+ """Delete a feed token"""
+ deleteFeedToken(token: String!): DeletedFeedToken
+
+ """Validate an user after registration"""
+ validateUser(token: String!): Login
+
+ """Resend registration confirmation token"""
+ resendConfirmationEmail(email: String!, locale: String): String
+
+ """Update an identity"""
+ updatePerson(
+ """
+ The avatar for the profile, either as an object or directly the ID of an existing Picture
+ """
+ avatar: PictureInput
+
+ """
+ The banner for the profile, either as an object or directly the ID of an existing Picture
+ """
+ banner: PictureInput
+ id: ID!
+
+ """The displayed name for this profile"""
+ name: String
+
+ """The summary for this profile"""
+ summary: String
+ ): Person
"""Create an event"""
createEvent(
@@ -974,95 +1087,6 @@ type RootMutationType {
visibility: EventVisibility = PUBLIC
): Event
- """Create a Feed Token"""
- createFeedToken(actorId: ID): FeedToken
-
- """Create a group"""
- createGroup(
- """
- The avatar for the group, either as an object or directly the ID of an existing Picture
- """
- avatar: PictureInput
-
- """
- The banner for the group, either as an object or directly the ID of an existing Picture
- """
- banner: PictureInput
-
- """The identity that creates the group"""
- creatorActorId: ID!
-
- """The displayed name for the group"""
- name: String
-
- """The name for the group"""
- preferredUsername: String!
-
- """The summary for the group"""
- summary: String = ""
- ): Group
-
- """Create a new person for user"""
- createPerson(
- """
- The avatar for the profile, either as an object or directly the ID of an existing Picture
- """
- avatar: PictureInput
-
- """
- The banner for the profile, either as an object or directly the ID of an existing Picture
- """
- banner: PictureInput
-
- """The displayed name for the new profile"""
- name: String = ""
- preferredUsername: String!
-
- """The summary for the new profile"""
- summary: String = ""
- ): Person
-
- """Create a report"""
- createReport(commentsIds: [ID] = [""], content: String, eventId: ID, reportedActorId: ID!, reporterActorId: ID!): Report
-
- """Create a note on a report"""
- createReportNote(content: String, moderatorId: ID!, reportId: ID!): ReportNote
-
- """Create an user"""
- createUser(email: String!, locale: String, password: String!): User
- deleteComment(actorId: ID!, commentId: ID!): DeletedObject
-
- """Delete an event"""
- deleteEvent(actorId: ID!, eventId: ID!): DeletedObject
-
- """Delete a feed token"""
- deleteFeedToken(token: String!): DeletedFeedToken
-
- """Delete a group"""
- deleteGroup(actorId: ID!, groupId: ID!): DeletedObject
-
- """Delete an identity"""
- deletePerson(id: ID!): Person
- deleteReportNote(moderatorId: ID!, noteId: ID!): DeletedObject
-
- """Join an event"""
- joinEvent(actorId: ID!, eventId: ID!): Participant
-
- """Join a group"""
- joinGroup(actorId: ID!, groupId: ID!): Member
-
- """Leave an event"""
- leaveEvent(actorId: ID!, eventId: ID!): DeletedParticipant
-
- """Leave an event"""
- leaveGroup(actorId: ID!, groupId: ID!): DeletedMember
-
- """Login an user"""
- login(email: String!, password: String!): Login
-
- """Refresh a token"""
- refreshToken(refreshToken: String!): RefreshedToken
-
"""Register a first profile on registration"""
registerPerson(
"""
@@ -1086,14 +1110,24 @@ type RootMutationType {
summary: String = ""
): Person
- """Resend registration confirmation token"""
- resendConfirmationEmail(email: String!, locale: String): String
+ """Accept a participation"""
+ updateParticipation(id: ID!, moderatorActorId: ID!, role: ParticipantRoleEnum!): Participant
- """Reset user password"""
- resetPassword(locale: String = "en", password: String!, token: String!): Login
+ """Delete a group"""
+ deleteGroup(actorId: ID!, groupId: ID!): DeletedObject
+ deleteComment(actorId: ID!, commentId: ID!): Comment
- """Send a link through email to reset user password"""
- sendResetPassword(email: String!, locale: String): String
+ """Create an user"""
+ createUser(email: String!, locale: String, password: String!): User
+
+ """Leave an event"""
+ leaveEvent(actorId: ID!, eventId: ID!): DeletedParticipant
+
+ """Refresh a token"""
+ refreshToken(refreshToken: String!): RefreshedToken
+
+ """Join a group"""
+ joinGroup(actorId: ID!, groupId: ID!): Member
"""Update an event"""
updateEvent(
@@ -1122,37 +1156,73 @@ type RootMutationType {
visibility: EventVisibility = PUBLIC
): Event
- """Accept a participation"""
- updateParticipation(id: ID!, moderatorActorId: ID!, role: ParticipantRoleEnum!): Participant
+ """Reset user password"""
+ resetPassword(locale: String = "en", password: String!, token: String!): Login
- """Update an identity"""
- updatePerson(
+ """Create a report"""
+ createReport(commentsIds: [ID] = [""], content: String, eventId: ID, forward: Boolean = false, reportedId: ID!, reporterId: ID!): Report
+
+ """Update a report"""
+ updateReportStatus(moderatorId: ID!, reportId: ID!, status: ReportStatus!): Report
+ deleteReportNote(moderatorId: ID!, noteId: ID!): DeletedObject
+
+ """Delete a relay subscription"""
+ removeRelay(address: String!): Follower
+
+ """Create a comment"""
+ createComment(actorId: ID!, eventId: ID, inReplyToCommentId: ID, text: String!): Comment
+
+ """Delete an identity"""
+ deletePerson(id: ID!): Person
+
+ """Reject a relay subscription"""
+ rejectRelay(address: String!): Follower
+
+ """Login an user"""
+ login(email: String!, password: String!): Login
+
+ """Leave an event"""
+ leaveGroup(actorId: ID!, groupId: ID!): DeletedMember
+
+ """Change an user password"""
+ changePassword(newPassword: String!, oldPassword: String!): User
+
+ """Add a relay subscription"""
+ addRelay(address: String!): Follower
+
+ """Join an event"""
+ joinEvent(actorId: ID!, eventId: ID!): Participant
+
+ """Create a group"""
+ createGroup(
"""
- The avatar for the profile, either as an object or directly the ID of an existing Picture
+ The avatar for the group, either as an object or directly the ID of an existing Picture
"""
avatar: PictureInput
"""
- The banner for the profile, either as an object or directly the ID of an existing Picture
+ The banner for the group, either as an object or directly the ID of an existing Picture
"""
banner: PictureInput
- id: ID!
- """The displayed name for this profile"""
+ """The identity that creates the group"""
+ creatorActorId: ID!
+
+ """The displayed name for the group"""
name: String
- """The summary for this profile"""
- summary: String
- ): Person
+ """The name for the group"""
+ preferredUsername: String!
- """Update a report"""
- updateReportStatus(moderatorId: ID!, reportId: ID!, status: ReportStatus!): Report
+ """The summary for the group"""
+ summary: String = ""
+ ): Group
- """Upload a picture"""
- uploadPicture(actorId: ID!, alt: String, file: Upload!, name: String!): Picture
+ """Send a link through email to reset user password"""
+ sendResetPassword(email: String!, locale: String): String
- """Validate an user after registration"""
- validateUser(token: String!): Login
+ """Create a Feed Token"""
+ createFeedToken(actorId: ID): FeedToken
}
"""
@@ -1196,6 +1266,8 @@ type RootQueryType {
"""Get a picture"""
picture(id: String!): Picture
+ relayFollowers(limit: Int = 10, page: Int = 1): PaginatedFollowerList
+ relayFollowings(direction: String = "desc", limit: Int = 10, orderBy: String = "updated_at", page: Int = 1): PaginatedFollowerList
"""Get a report by id"""
report(id: ID!): Report
@@ -1231,6 +1303,10 @@ type RootQueryType {
users(direction: SortDirection = DESC, limit: Int = 10, page: Int = 1, sort: SortableUserField = ID): Users
}
+type RootSubscriptionType {
+ eventPersonParticipationChanged(personId: ID!): Person
+}
+
"""The list of possible options for the event's status"""
enum SortableUserField {
ID
diff --git a/test/fixtures/mastodon-delete-user.json b/test/fixtures/mastodon-delete-user.json
new file mode 100644
index 000000000..213354f02
--- /dev/null
+++ b/test/fixtures/mastodon-delete-user.json
@@ -0,0 +1,24 @@
+{
+ "type": "Delete",
+ "object": {
+ "type": "Person",
+ "id": "https://framapiaf.org/users/admin",
+ "atomUri": "https://framapiaf.org/users/admin"
+ },
+ "id": "https://framapiaf.org/users/admin#delete",
+ "actor": "https://framapiaf.org/users/admin",
+ "@context": [
+ {
+ "toot": "http://joinmastodon.org/ns#",
+ "sensitive": "as:sensitive",
+ "ostatus": "http://ostatus.org#",
+ "movedTo": "as:movedTo",
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+ "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
+ "conversation": "ostatus:conversation",
+ "atomUri": "ostatus:atomUri",
+ "Hashtag": "as:Hashtag",
+ "Emoji": "toot:Emoji"
+ }
+ ]
+}
diff --git a/test/fixtures/mobilizon-join-activity.json b/test/fixtures/mobilizon-join-activity.json
index f2669e3aa..8580ccf4a 100644
--- a/test/fixtures/mobilizon-join-activity.json
+++ b/test/fixtures/mobilizon-join-activity.json
@@ -11,12 +11,31 @@
"actor": "http://mobilizon2.test/@admin",
"@context": [
"https://www.w3.org/ns/activitystreams",
- "https://w3id.org/security/v1",
+ "https://litepub.social/litepub/context.jsonld",
{
- "sensitive": "as:sensitive",
- "movedTo": "as:movedTo",
- "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
- "Hashtag": "as:Hashtag"
+ "Hashtag": "as:Hashtag",
+ "category": "sc:category",
+ "ical": "http://www.w3.org/2002/12/cal/ical#",
+ "joinMode": {
+ "@id": "mz:joinMode",
+ "@type": "mz:joinModeType"
+ },
+ "joinModeType": {
+ "@id": "mz:joinModeType",
+ "@type": "rdfs:Class"
+ },
+ "maximumAttendeeCapacity": "sc:maximumAttendeeCapacity",
+ "mz": "https://joinmobilizon.org/ns#",
+ "repliesModerationOption": {
+ "@id": "mz:repliesModerationOption",
+ "@type": "mz:repliesModerationOptionType"
+ },
+ "repliesModerationOptionType": {
+ "@id": "mz:repliesModerationOptionType",
+ "@type": "rdfs:Class"
+ },
+ "sc": "http://schema.org#",
+ "uuid": "sc:identifier"
}
]
}
\ No newline at end of file
diff --git a/test/fixtures/mobilizon-leave-activity.json b/test/fixtures/mobilizon-leave-activity.json
index 10d157987..58e39ffcd 100644
--- a/test/fixtures/mobilizon-leave-activity.json
+++ b/test/fixtures/mobilizon-leave-activity.json
@@ -11,12 +11,31 @@
"actor": "http://mobilizon2.test/@admin",
"@context": [
"https://www.w3.org/ns/activitystreams",
- "https://w3id.org/security/v1",
+ "https://litepub.social/litepub/context.jsonld",
{
- "sensitive": "as:sensitive",
- "movedTo": "as:movedTo",
- "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
- "Hashtag": "as:Hashtag"
+ "Hashtag": "as:Hashtag",
+ "category": "sc:category",
+ "ical": "http://www.w3.org/2002/12/cal/ical#",
+ "joinMode": {
+ "@id": "mz:joinMode",
+ "@type": "mz:joinModeType"
+ },
+ "joinModeType": {
+ "@id": "mz:joinModeType",
+ "@type": "rdfs:Class"
+ },
+ "maximumAttendeeCapacity": "sc:maximumAttendeeCapacity",
+ "mz": "https://joinmobilizon.org/ns#",
+ "repliesModerationOption": {
+ "@id": "mz:repliesModerationOption",
+ "@type": "mz:repliesModerationOptionType"
+ },
+ "repliesModerationOptionType": {
+ "@id": "mz:repliesModerationOptionType",
+ "@type": "rdfs:Class"
+ },
+ "sc": "http://schema.org#",
+ "uuid": "sc:identifier"
}
]
}
\ No newline at end of file
diff --git a/test/fixtures/mobilizon-post-activity.json b/test/fixtures/mobilizon-post-activity.json
index 21a9ef442..1cba4dd04 100644
--- a/test/fixtures/mobilizon-post-activity.json
+++ b/test/fixtures/mobilizon-post-activity.json
@@ -1,13 +1,30 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
- "https://w3id.org/security/v1",
+ "https://litepub.social/litepub/context.jsonld",
{
- "mblzn": "https://joinmobilizon.org/ns#",
"Hashtag": "as:Hashtag",
+ "category": "sc:category",
+ "ical": "http://www.w3.org/2002/12/cal/ical#",
+ "joinMode": {
+ "@id": "mz:joinMode",
+ "@type": "mz:joinModeType"
+ },
+ "joinModeType": {
+ "@id": "mz:joinModeType",
+ "@type": "rdfs:Class"
+ },
+ "maximumAttendeeCapacity": "sc:maximumAttendeeCapacity",
+ "mz": "https://joinmobilizon.org/ns#",
+ "repliesModerationOption": {
+ "@id": "mz:repliesModerationOption",
+ "@type": "mz:repliesModerationOptionType"
+ },
+ "repliesModerationOptionType": {
+ "@id": "mz:repliesModerationOptionType",
+ "@type": "rdfs:Class"
+ },
"sc": "http://schema.org#",
- "Place": "sc:Place",
- "PostalAddress": "sc:PostalAddress",
"uuid": "sc:identifier"
}
],
diff --git a/test/fixtures/vcr_cassettes/activity_pub/activity_object_bogus.json b/test/fixtures/vcr_cassettes/activity_pub/activity_object_bogus.json
new file mode 100644
index 000000000..f6d548a8f
--- /dev/null
+++ b/test/fixtures/vcr_cassettes/activity_pub/activity_object_bogus.json
@@ -0,0 +1,116 @@
+[
+ {
+ "request": {
+ "body": "",
+ "headers": {
+ "Accept": "application/activity+json"
+ },
+ "method": "get",
+ "options": {
+ "follow_redirect": "true"
+ },
+ "request_body": "",
+ "url": "https://framapiaf.org/users/admin"
+ },
+ "response": {
+ "binary": false,
+ "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"toot\":\"http://joinmastodon.org/ns#\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"alsoKnownAs\":{\"@id\":\"as:alsoKnownAs\",\"@type\":\"@id\"},\"movedTo\":{\"@id\":\"as:movedTo\",\"@type\":\"@id\"},\"schema\":\"http://schema.org#\",\"PropertyValue\":\"schema:PropertyValue\",\"value\":\"schema:value\",\"IdentityProof\":\"toot:IdentityProof\",\"discoverable\":\"toot:discoverable\",\"focalPoint\":{\"@container\":\"@list\",\"@id\":\"toot:focalPoint\"}}],\"id\":\"https://framapiaf.org/users/admin\",\"type\":\"Service\",\"following\":\"https://framapiaf.org/users/admin/following\",\"followers\":\"https://framapiaf.org/users/admin/followers\",\"inbox\":\"https://framapiaf.org/users/admin/inbox\",\"outbox\":\"https://framapiaf.org/users/admin/outbox\",\"featured\":\"https://framapiaf.org/users/admin/collections/featured\",\"preferredUsername\":\"admin\",\"name\":\"Administrateur\",\"summary\":\"\\u003cp\\u003eJe ne suis qu\\u0026apos;un compte inutile. Merci nous de contacter via \\u003ca href=\\\"https://contact.framasoft.org/\\\" rel=\\\"nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003econtact.framasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/p\\u003e\",\"url\":\"https://framapiaf.org/@admin\",\"manuallyApprovesFollowers\":false,\"discoverable\":null,\"publicKey\":{\"id\":\"https://framapiaf.org/users/admin#main-key\",\"owner\":\"https://framapiaf.org/users/admin\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyHaU/AZ5dWtSxZXkPa89\\nDUQ4z+JQHGGUG/xkGuq0v8P6qJfQqtHPBO5vH0IQJqluXWQS96gqTwjZnYevcpNA\\nveYv0K25DWszx5Ehz6JX2/sSvu2rNUcQ3YZvSjdo/Yy1u5Fuc5lLmvw8uFzXYekD\\nWovTMOnp4mIKpVEm/G/v4w8jvFEKw88h743vwaEIim88GEQItMxzGAV6zSqV1DWO\\nLxtoRsinslJYfAG46ex4YUATFveWvOUeWk5W1sEa5f3c0moaTmBM/PAAo8vLxhlw\\nJhsHihsCH+BcXKVMjW8OCqYYqISMxEifUBX63HcJt78ELHpOuc1c2eG59PomtTjQ\\nywIDAQAB\\n-----END PUBLIC KEY-----\\n\"},\"tag\":[],\"attachment\":[{\"type\":\"PropertyValue\",\"name\":\"News\",\"value\":\"\\u003cspan class=\\\"h-card\\\"\\u003e\\u003ca href=\\\"https://framapiaf.org/@Framasoft\\\" class=\\\"u-url mention\\\"\\u003e@\\u003cspan\\u003eFramasoft\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/span\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Support\",\"value\":\"\\u003ca href=\\\"https://contact.framasoft.org/\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003econtact.framasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Soutenir\",\"value\":\"\\u003ca href=\\\"https://soutenir.framasoft.org/\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003esoutenir.framasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Site\",\"value\":\"\\u003ca href=\\\"https://framasoft.org/\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003eframasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"}],\"endpoints\":{\"sharedInbox\":\"https://framapiaf.org/inbox\"},\"icon\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/avatars/000/000/002/original/85fbb27ad5e3cf71.jpg\"},\"image\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/headers/000/000/002/original/6aba75f1ab1ab6de.jpg\"}}",
+ "headers": {
+ "Date": "Sun, 15 Dec 2019 20:24:11 GMT",
+ "Content-Type": "application/activity+json; charset=utf-8",
+ "Transfer-Encoding": "chunked",
+ "Connection": "keep-alive",
+ "Server": "Mastodon",
+ "X-Frame-Options": "DENY",
+ "X-Content-Type-Options": "nosniff",
+ "X-XSS-Protection": "1; mode=block",
+ "Vary": "Accept, Accept-Encoding, Origin",
+ "Cache-Control": "max-age=180, public",
+ "ETag": "W/\"773e09a2a60446fe74d997858877f7e0\"",
+ "Set-Cookie": "_mastodon_session=XoZzbDOQtPbBNtbz7WoWZ4ZHTySKGQUeb1Hl3%2BxqZV%2FvxVEOMGMjKArqNF%2F78EJaF5TS%2FKcHPhwonEfsI5cz--jZG8CkvbBBmaJMDx--WRqeW2u0rVAHoNKcMNfLYA%3D%3D; path=/; secure; HttpOnly",
+ "X-Request-Id": "2a29bc1c-9dc8-4e36-b6d5-106c2f649959",
+ "X-Runtime": "0.012324",
+ "X-Cached": "MISS",
+ "Strict-Transport-Security": "max-age=31536000"
+ },
+ "status_code": 200,
+ "type": "ok"
+ }
+ },
+ {
+ "request": {
+ "body": "",
+ "headers": {
+ "Accept": "application/activity+json",
+ "Date": "Sun, 15 Dec 2019 20:24:11 GMT",
+ "Signature": "keyId=\"http://mobilizon.test/relay#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) date host\",signature=\"C6AGJG5xDdDBYfoAgBaL0iG0/Ru9qpvUij5vvONxJJpcjwc4LIePLaCid4LWK4aiY/z67M+LgopmD7xRn2Qht+Cyu3Kh38t5dS8EI0RWR4JesRyBauCZpzbRJG2w5SES4BPt53+5AuvPZZ81BBgPYF4A7ITBn550NLocesuFFsJJZHwfNqRCUm4cmx57/tnLBr0S4w/VDn6iQ3TBSlXdUJ7N9Za9y7p+vfkFT2PqSXu55HdWLX5NvaiVl2m7JKBCxCrB3i4Lr/Og5bsKhi6LiUoc7Lp0LX1tNftp6NOGgBIo0982NQ0v2jGsMj+eGU2stDl6bz3Z9SRnyyxirz+fag==\""
+ },
+ "method": "get",
+ "options": {
+ "follow_redirect": "true",
+ "recv_timeout": 20000,
+ "connect_timeout": 10000
+ },
+ "request_body": "",
+ "url": "https://info.pleroma.site/activity.json"
+ },
+ "response": {
+ "binary": false,
+ "body": "{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"actor\": \"https://queer.hacktivis.me/users/lanodan\",\n \"announcement_count\": 3,\n \"announcements\": [\n \"https://io.markegli.com/users/mark\",\n \"https://voluntaryism.club/users/sevvie\",\n \"https://pleroma.pla1.net/users/pla\"\n ],\n \"attachment\": [],\n \"attributedTo\": \"https://queer.hacktivis.me/users/lanodan\",\n \"content\": \"this post was not actually written by Haelwenn \",\n \"id\": \"https://info.pleroma.site/activity.json\",\n \"published\": \"2018-09-01T22:15:00Z\",\n \"tag\": [],\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\"\n ],\n \"type\": \"Note\"\n}\n",
+ "headers": {
+ "Server": "nginx",
+ "Date": "Sun, 15 Dec 2019 20:24:11 GMT",
+ "Content-Type": "application/json",
+ "Content-Length": "750",
+ "Last-Modified": "Sat, 01 Sep 2018 22:56:24 GMT",
+ "Connection": "keep-alive",
+ "ETag": "\"5b8b1918-2ee\"",
+ "Accept-Ranges": "bytes"
+ },
+ "status_code": 200,
+ "type": "ok"
+ }
+ },
+ {
+ "request": {
+ "body": "",
+ "headers": {
+ "Accept": "application/activity+json"
+ },
+ "method": "get",
+ "options": {
+ "follow_redirect": "true"
+ },
+ "request_body": "",
+ "url": "https://queer.hacktivis.me/users/lanodan"
+ },
+ "response": {
+ "binary": false,
+ "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://queer.hacktivis.me/schemas/litepub-0.1.jsonld\",{\"@language\":\"und\"}],\"attachment\":[],\"discoverable\":false,\"endpoints\":{\"oauthAuthorizationEndpoint\":\"https://queer.hacktivis.me/oauth/authorize\",\"oauthRegistrationEndpoint\":\"https://queer.hacktivis.me/api/v1/apps\",\"oauthTokenEndpoint\":\"https://queer.hacktivis.me/oauth/token\",\"sharedInbox\":\"https://queer.hacktivis.me/inbox\",\"uploadMedia\":\"https://queer.hacktivis.me/api/ap/upload_media\"},\"followers\":\"https://queer.hacktivis.me/users/lanodan/followers\",\"following\":\"https://queer.hacktivis.me/users/lanodan/following\",\"icon\":{\"type\":\"Image\",\"url\":\"https://queer.hacktivis.me/media/c8e81887-7d81-4cdc-91b3-c624ea79e6c9/425f089961270eff91b66d45f8faeeb12a725a5f87a6a52bfc54c43bd89f5fe9.png\"},\"id\":\"https://queer.hacktivis.me/users/lanodan\",\"image\":{\"type\":\"Image\",\"url\":\"https://queer.hacktivis.me/media/37b6ce56-8c24-4e64-bd70-a76e84ab0c69/53a48a3a49ed5e5637a84e4f3663df17f8d764244bbc1027ba03cfc446e8b7bd.jpg\"},\"inbox\":\"https://queer.hacktivis.me/users/lanodan/inbox\",\"manuallyApprovesFollowers\":true,\"name\":\"Haelwenn /ɛlwən/ 🐺\",\"outbox\":\"https://queer.hacktivis.me/users/lanodan/outbox\",\"preferredUsername\":\"lanodan\",\"publicKey\":{\"id\":\"https://queer.hacktivis.me/users/lanodan#main-key\",\"owner\":\"https://queer.hacktivis.me/users/lanodan\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsWOgdjSMc010qvxC3njI\\nXJlFWMJ5gJ8QXCW/PajYdsHPM6d+jxBNJ6zp9/tIRa2m7bWHTSkuHQ7QthOpt6vu\\n+dAWpKRLS607SPLItn/qUcyXvgN+H8shfyhMxvkVs9jXdtlBsLUVE7UNpN0dxzqe\\nI79QWbf7o4amgaIWGRYB+OYMnIxKt+GzIkivZdSVSYjfxNnBYkMCeUxm5EpPIxKS\\nP5bBHAVRRambD5NUmyKILuC60/rYuc/C+vmgpY2HCWFS2q6o34dPr9enwL6t4b3m\\nS1t/EJHk9rGaaDqSGkDEfyQI83/7SDebWKuETMKKFLZi1vMgQIFuOYCIhN6bIiZm\\npQIDAQAB\\n-----END PUBLIC KEY-----\\n\\n\"},\"summary\":\"--- Website: https://hacktivis.me/ Pronouns: she/fae, elle/iel Lang: en, fr, (LSF), ... ``` 🦊🦄⚧🂡ⓥ :anarchy: 👿🐧 :gentoo: Pleroma dev (backend, mastofe)
banner from: https://soc.flyingcube.tech/objects/56f79be2-9013-4559-9826-f7dc392417db Federation-bots: #nobot\",\"tag\":[{\"icon\":{\"type\":\"Image\",\"url\":\"https://queer.hacktivis.me/emoji/custom/symbols/anarchy.png\"},\"name\":\":anarchy:\",\"type\":\"Emoji\"},{\"icon\":{\"type\":\"Image\",\"url\":\"https://queer.hacktivis.me/emoji/custom/gentoo.png\"},\"name\":\":gentoo:\",\"type\":\"Emoji\"}],\"type\":\"Person\",\"url\":\"https://queer.hacktivis.me/users/lanodan\"}",
+ "headers": {
+ "Server": "nginx/1.16.1",
+ "Date": "Sun, 15 Dec 2019 20:24:12 GMT",
+ "Content-Type": "application/activity+json; charset=utf-8",
+ "Content-Length": "2693",
+ "Connection": "keep-alive",
+ "Keep-Alive": "timeout=20",
+ "access-control-allow-credentials": "true",
+ "access-control-allow-origin": "*",
+ "access-control-expose-headers": "Link,X-RateLimit-Reset,X-RateLimit-Limit,X-RateLimit-Remaining,X-Request-Id,Idempotency-Key",
+ "cache-control": "max-age=0, private, must-revalidate",
+ "content-security-policy": "default-src 'none'; base-uri 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; manifest-src 'self'; connect-src 'self' https://queer.hacktivis.me wss://queer.hacktivis.me; script-src 'self'; upgrade-insecure-requests;",
+ "expect-ct": "enforce, max-age=2592000",
+ "referrer-policy": "no-referrer",
+ "strict-transport-security": "max-age=31536000; includeSubDomains",
+ "x-content-type-options": "nosniff",
+ "x-download-options": "noopen",
+ "x-frame-options": "DENY",
+ "x-permitted-cross-domain-policies": "none",
+ "x-request-id": "FeClJfdJwAn7WY0ABtmh",
+ "x-xss-protection": "1; mode=block"
+ },
+ "status_code": 200,
+ "type": "ok"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/fixtures/vcr_cassettes/activity_pub/event_update_activities.json b/test/fixtures/vcr_cassettes/activity_pub/event_update_activities.json
index 20fbdb575..700e12e86 100644
--- a/test/fixtures/vcr_cassettes/activity_pub/event_update_activities.json
+++ b/test/fixtures/vcr_cassettes/activity_pub/event_update_activities.json
@@ -17,7 +17,7 @@
"body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://litepub.github.io/litepub/context.jsonld\",{\"Hashtag\":\"as:Hashtag\",\"category\":\"sc:category\",\"sc\":\"http://schema.org#\",\"uuid\":\"sc:identifier\"}],\"endpoints\":{\"sharedInbox\":\"https://test.mobilizon.org/inbox\"},\"followers\":\"https://test.mobilizon.org/@Alicia/followers\",\"following\":\"https://test.mobilizon.org/@Alicia/following\",\"id\":\"https://test.mobilizon.org/@Alicia\",\"inbox\":\"https://test.mobilizon.org/@Alicia/inbox\",\"manuallyApprovesFollowers\":false,\"name\":\"Alicia\",\"outbox\":\"https://test.mobilizon.org/@Alicia/outbox\",\"preferredUsername\":\"Alicia\",\"publicKey\":{\"id\":\"https://test.mobilizon.org/@Alicia#main-key\",\"owner\":\"https://test.mobilizon.org/@Alicia\",\"publicKeyPem\":\"-----BEGIN RSA PUBLIC KEY-----\\nMIIBCgKCAQEAvb+emDoC6FCVpfo9Bh608sVsOK+8fun3UIqaR+jr+DZCAjp8ihwa\\nFkXaeOQ744MVS2YdzBEyIlk3sSYD9GezF+zoMbbA8FcnJ5jZhnneRR7ZrEg/cpNx\\nKFVA2ZoQrAABwpnA1iv7ciLoYZKPTDpIZ7Ue5l/k1bYcfTy0d4F3c8YAayWftSWj\\nHy3FK2kZDLdKfpRyfn5a4UI6sao4uD/rHno47g8tPPVA74BBpaTntJfbTWqiR8Vn\\nmNGAzy3+47pVeeg6Rd+AALohzBpHPW3TlJ75mqxPDXk7aDRYXihHrswf4MmKuaXc\\nXdoCu6uxQp41Xf3jVYD+AWw60tv2Oj/d4wIDAQAB\\n-----END RSA PUBLIC KEY-----\\n\\n\"},\"summary\":\"J'aime le karaté, les mangas, coder en python.\",\"type\":\"Person\",\"url\":\"https://test.mobilizon.org/@Alicia\"}",
"headers": {
"Server": "nginx/1.14.2",
- "Date": "Sun, 17 Nov 2019 18:12:35 GMT",
+ "Date": "Mon, 09 Dec 2019 17:24:25 GMT",
"Content-Type": "application/activity+json; charset=utf-8",
"Content-Length": "1293",
"Connection": "keep-alive",
@@ -25,12 +25,50 @@
"access-control-allow-origin": "*",
"access-control-expose-headers": "",
"cache-control": "max-age=0, private, must-revalidate",
- "x-request-id": "FdgFt2eS9ln4-7YACVtC",
+ "x-request-id": "Fd7D2yizsbKxZroABLnC",
"Strict-Transport-Security": "max-age=63072000; includeSubDomains",
"X-Content-Type-Options": "nosniff"
},
"status_code": 200,
"type": "ok"
}
+ },
+ {
+ "request": {
+ "body": "",
+ "headers": {
+ "Accept": "application/activity+json"
+ },
+ "method": "get",
+ "options": {
+ "follow_redirect": "true"
+ },
+ "request_body": "",
+ "url": "https://framapiaf.org/users/tcit"
+ },
+ "response": {
+ "binary": false,
+ "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"toot\":\"http://joinmastodon.org/ns#\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"alsoKnownAs\":{\"@id\":\"as:alsoKnownAs\",\"@type\":\"@id\"},\"movedTo\":{\"@id\":\"as:movedTo\",\"@type\":\"@id\"},\"schema\":\"http://schema.org#\",\"PropertyValue\":\"schema:PropertyValue\",\"value\":\"schema:value\",\"IdentityProof\":\"toot:IdentityProof\",\"discoverable\":\"toot:discoverable\",\"Hashtag\":\"as:Hashtag\",\"focalPoint\":{\"@container\":\"@list\",\"@id\":\"toot:focalPoint\"}}],\"id\":\"https://framapiaf.org/users/tcit\",\"type\":\"Person\",\"following\":\"https://framapiaf.org/users/tcit/following\",\"followers\":\"https://framapiaf.org/users/tcit/followers\",\"inbox\":\"https://framapiaf.org/users/tcit/inbox\",\"outbox\":\"https://framapiaf.org/users/tcit/outbox\",\"featured\":\"https://framapiaf.org/users/tcit/collections/featured\",\"preferredUsername\":\"tcit\",\"name\":\"💼 Thomas Citharel (Work)\",\"summary\":\"\\u003cp\\u003e\\u003ca href=\\\"https://framapiaf.org/tags/Framasoft\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eFramasoft\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/FreeSoftware\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eFreeSoftware\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Activism\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eActivism\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/wallabag\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003ewallabag\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Federation\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eFederation\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Nextcloud\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eNextcloud\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Mobilizon\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eMobilizon\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Libre\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eLibre\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/p\\u003e\",\"url\":\"https://framapiaf.org/@tcit\",\"manuallyApprovesFollowers\":false,\"discoverable\":true,\"publicKey\":{\"id\":\"https://framapiaf.org/users/tcit#main-key\",\"owner\":\"https://framapiaf.org/users/tcit\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApscVCt06lrIiB5jT6Kqk\\nZZwPVoPkhR7HzoTGb8rnklZuOyP4goHIuBDnurklztkmDCaM7DbsUWAPgRVtwWFE\\nWuQrOenb7BPRe/m99pJfUTkBQU3IeuRMD/5Fc3OTIhHQOltTSiB900srCUxjysfw\\nnV5JFciCz8YAXTNJZD34qyv8DbtC/pCJM7wMd9Hl3ohxSPETa6CJUaTdlNwlYJa2\\nMOMCj6/7Iv5oAg14FT9lwqS5lF7jPHk9Z7PNc2wPmNVgIYA2n9d5k7JY8TdM8iu4\\nHLnIbJuqDd1uitlYgy1qsdsxjv4U2Y7Nytc+3ZKHtGsCzUltYL5kC7uWrFpGoWo1\\n0QIDAQAB\\n-----END PUBLIC KEY-----\\n\"},\"tag\":[{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/activism\",\"name\":\"#activism\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/federation\",\"name\":\"#federation\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/framasoft\",\"name\":\"#framasoft\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/freesoftware\",\"name\":\"#freesoftware\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/libre\",\"name\":\"#libre\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/mobilizon\",\"name\":\"#mobilizon\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/nextcloud\",\"name\":\"#nextcloud\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/wallabag\",\"name\":\"#wallabag\"}],\"attachment\":[{\"type\":\"PropertyValue\",\"name\":\"Personal account\",\"value\":\"\\u003ca href=\\\"https://social.tcit.fr/@tcit\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003esocial.tcit.fr/@tcit\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Location\",\"value\":\"Nantes, France\"},{\"type\":\"PropertyValue\",\"name\":\"Works at\",\"value\":\"\\u003cspan class=\\\"h-card\\\"\\u003e\\u003ca href=\\\"https://framapiaf.org/@Framasoft\\\" class=\\\"u-url mention\\\"\\u003e@\\u003cspan\\u003eFramasoft\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/span\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Website\",\"value\":\"\\u003ca href=\\\"https://tcit.fr\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003etcit.fr\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"IdentityProof\",\"name\":\"tcit\",\"signatureAlgorithm\":\"keybase\",\"signatureValue\":\"f66b45be42803010fe2f4d80e729b41bbe5ed056e2ff1286b7b5a5ea9c724cc70f\"}],\"endpoints\":{\"sharedInbox\":\"https://framapiaf.org/inbox\"},\"icon\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/avatars/000/000/001/original/da0cad7ffd20eb61.jpg\"},\"image\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/headers/000/000/001/original/198d058b3086d82d.jpg\"}}",
+ "headers": {
+ "Date": "Mon, 09 Dec 2019 17:24:25 GMT",
+ "Content-Type": "application/activity+json; charset=utf-8",
+ "Transfer-Encoding": "chunked",
+ "Connection": "keep-alive",
+ "Server": "Mastodon",
+ "X-Frame-Options": "DENY",
+ "X-Content-Type-Options": "nosniff",
+ "X-XSS-Protection": "1; mode=block",
+ "Vary": "Accept, Accept-Encoding, Origin",
+ "Cache-Control": "max-age=180, public",
+ "ETag": "W/\"11665b12333a8c7708de7b17f58147b2\"",
+ "Set-Cookie": "_mastodon_session=l2BJyxnUWpNcZQ0u%2FaLJvf95IOy5b4PC1p1MVB7IGImBVhYbt6c0v6dcZcbtJzV%2FhPHF649GTTHeMixcyk1w--6lGoR%2F%2FAOMyZRQJi--fGHpQLKpoTLyRrgQQ7WW8g%3D%3D; path=/; secure; HttpOnly",
+ "X-Request-Id": "7d05233c-4a2d-4cf9-bcb6-6f405f6a370a",
+ "X-Runtime": "0.011783",
+ "X-Cached": "MISS",
+ "Strict-Transport-Security": "max-age=31536000"
+ },
+ "status_code": 200,
+ "type": "ok"
+ }
}
]
\ No newline at end of file
diff --git a/test/fixtures/vcr_cassettes/activity_pub/fetch_mobilizon_post_activity.json b/test/fixtures/vcr_cassettes/activity_pub/fetch_mobilizon_post_activity.json
index 051ac17b6..c099ca51f 100644
--- a/test/fixtures/vcr_cassettes/activity_pub/fetch_mobilizon_post_activity.json
+++ b/test/fixtures/vcr_cassettes/activity_pub/fetch_mobilizon_post_activity.json
@@ -17,7 +17,7 @@
"body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://litepub.github.io/litepub/context.jsonld\",{\"Hashtag\":\"as:Hashtag\",\"category\":\"sc:category\",\"sc\":\"http://schema.org#\",\"uuid\":\"sc:identifier\"}],\"endpoints\":{\"sharedInbox\":\"https://test.mobilizon.org/inbox\"},\"followers\":\"https://test.mobilizon.org/@Alicia/followers\",\"following\":\"https://test.mobilizon.org/@Alicia/following\",\"id\":\"https://test.mobilizon.org/@Alicia\",\"inbox\":\"https://test.mobilizon.org/@Alicia/inbox\",\"manuallyApprovesFollowers\":false,\"name\":\"Alicia\",\"outbox\":\"https://test.mobilizon.org/@Alicia/outbox\",\"preferredUsername\":\"Alicia\",\"publicKey\":{\"id\":\"https://test.mobilizon.org/@Alicia#main-key\",\"owner\":\"https://test.mobilizon.org/@Alicia\",\"publicKeyPem\":\"-----BEGIN RSA PUBLIC KEY-----\\nMIIBCgKCAQEAvb+emDoC6FCVpfo9Bh608sVsOK+8fun3UIqaR+jr+DZCAjp8ihwa\\nFkXaeOQ744MVS2YdzBEyIlk3sSYD9GezF+zoMbbA8FcnJ5jZhnneRR7ZrEg/cpNx\\nKFVA2ZoQrAABwpnA1iv7ciLoYZKPTDpIZ7Ue5l/k1bYcfTy0d4F3c8YAayWftSWj\\nHy3FK2kZDLdKfpRyfn5a4UI6sao4uD/rHno47g8tPPVA74BBpaTntJfbTWqiR8Vn\\nmNGAzy3+47pVeeg6Rd+AALohzBpHPW3TlJ75mqxPDXk7aDRYXihHrswf4MmKuaXc\\nXdoCu6uxQp41Xf3jVYD+AWw60tv2Oj/d4wIDAQAB\\n-----END RSA PUBLIC KEY-----\\n\\n\"},\"summary\":\"J'aime le karaté, les mangas, coder en python.\",\"type\":\"Person\",\"url\":\"https://test.mobilizon.org/@Alicia\"}",
"headers": {
"Server": "nginx/1.14.2",
- "Date": "Sun, 17 Nov 2019 18:00:51 GMT",
+ "Date": "Mon, 09 Dec 2019 17:24:24 GMT",
"Content-Type": "application/activity+json; charset=utf-8",
"Content-Length": "1293",
"Connection": "keep-alive",
@@ -25,12 +25,50 @@
"access-control-allow-origin": "*",
"access-control-expose-headers": "",
"cache-control": "max-age=0, private, must-revalidate",
- "x-request-id": "FdgFE5DoOmZXNz8ACVni",
+ "x-request-id": "Fd7D2v45MCtfaxgAB7bh",
"Strict-Transport-Security": "max-age=63072000; includeSubDomains",
"X-Content-Type-Options": "nosniff"
},
"status_code": 200,
"type": "ok"
}
+ },
+ {
+ "request": {
+ "body": "",
+ "headers": {
+ "Accept": "application/activity+json"
+ },
+ "method": "get",
+ "options": {
+ "follow_redirect": "true"
+ },
+ "request_body": "",
+ "url": "https://framapiaf.org/users/tcit"
+ },
+ "response": {
+ "binary": false,
+ "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"toot\":\"http://joinmastodon.org/ns#\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"alsoKnownAs\":{\"@id\":\"as:alsoKnownAs\",\"@type\":\"@id\"},\"movedTo\":{\"@id\":\"as:movedTo\",\"@type\":\"@id\"},\"schema\":\"http://schema.org#\",\"PropertyValue\":\"schema:PropertyValue\",\"value\":\"schema:value\",\"IdentityProof\":\"toot:IdentityProof\",\"discoverable\":\"toot:discoverable\",\"Hashtag\":\"as:Hashtag\",\"focalPoint\":{\"@container\":\"@list\",\"@id\":\"toot:focalPoint\"}}],\"id\":\"https://framapiaf.org/users/tcit\",\"type\":\"Person\",\"following\":\"https://framapiaf.org/users/tcit/following\",\"followers\":\"https://framapiaf.org/users/tcit/followers\",\"inbox\":\"https://framapiaf.org/users/tcit/inbox\",\"outbox\":\"https://framapiaf.org/users/tcit/outbox\",\"featured\":\"https://framapiaf.org/users/tcit/collections/featured\",\"preferredUsername\":\"tcit\",\"name\":\"💼 Thomas Citharel (Work)\",\"summary\":\"\\u003cp\\u003e\\u003ca href=\\\"https://framapiaf.org/tags/Framasoft\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eFramasoft\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/FreeSoftware\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eFreeSoftware\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Activism\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eActivism\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/wallabag\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003ewallabag\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Federation\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eFederation\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Nextcloud\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eNextcloud\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Mobilizon\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eMobilizon\\u003c/span\\u003e\\u003c/a\\u003e \\u003ca href=\\\"https://framapiaf.org/tags/Libre\\\" class=\\\"mention hashtag\\\" rel=\\\"tag\\\"\\u003e#\\u003cspan\\u003eLibre\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/p\\u003e\",\"url\":\"https://framapiaf.org/@tcit\",\"manuallyApprovesFollowers\":false,\"discoverable\":true,\"publicKey\":{\"id\":\"https://framapiaf.org/users/tcit#main-key\",\"owner\":\"https://framapiaf.org/users/tcit\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApscVCt06lrIiB5jT6Kqk\\nZZwPVoPkhR7HzoTGb8rnklZuOyP4goHIuBDnurklztkmDCaM7DbsUWAPgRVtwWFE\\nWuQrOenb7BPRe/m99pJfUTkBQU3IeuRMD/5Fc3OTIhHQOltTSiB900srCUxjysfw\\nnV5JFciCz8YAXTNJZD34qyv8DbtC/pCJM7wMd9Hl3ohxSPETa6CJUaTdlNwlYJa2\\nMOMCj6/7Iv5oAg14FT9lwqS5lF7jPHk9Z7PNc2wPmNVgIYA2n9d5k7JY8TdM8iu4\\nHLnIbJuqDd1uitlYgy1qsdsxjv4U2Y7Nytc+3ZKHtGsCzUltYL5kC7uWrFpGoWo1\\n0QIDAQAB\\n-----END PUBLIC KEY-----\\n\"},\"tag\":[{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/activism\",\"name\":\"#activism\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/federation\",\"name\":\"#federation\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/framasoft\",\"name\":\"#framasoft\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/freesoftware\",\"name\":\"#freesoftware\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/libre\",\"name\":\"#libre\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/mobilizon\",\"name\":\"#mobilizon\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/nextcloud\",\"name\":\"#nextcloud\"},{\"type\":\"Hashtag\",\"href\":\"https://framapiaf.org/explore/wallabag\",\"name\":\"#wallabag\"}],\"attachment\":[{\"type\":\"PropertyValue\",\"name\":\"Personal account\",\"value\":\"\\u003ca href=\\\"https://social.tcit.fr/@tcit\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003esocial.tcit.fr/@tcit\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Location\",\"value\":\"Nantes, France\"},{\"type\":\"PropertyValue\",\"name\":\"Works at\",\"value\":\"\\u003cspan class=\\\"h-card\\\"\\u003e\\u003ca href=\\\"https://framapiaf.org/@Framasoft\\\" class=\\\"u-url mention\\\"\\u003e@\\u003cspan\\u003eFramasoft\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/span\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Website\",\"value\":\"\\u003ca href=\\\"https://tcit.fr\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003etcit.fr\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"IdentityProof\",\"name\":\"tcit\",\"signatureAlgorithm\":\"keybase\",\"signatureValue\":\"f66b45be42803010fe2f4d80e729b41bbe5ed056e2ff1286b7b5a5ea9c724cc70f\"}],\"endpoints\":{\"sharedInbox\":\"https://framapiaf.org/inbox\"},\"icon\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/avatars/000/000/001/original/da0cad7ffd20eb61.jpg\"},\"image\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/headers/000/000/001/original/198d058b3086d82d.jpg\"}}",
+ "headers": {
+ "Date": "Mon, 09 Dec 2019 17:24:25 GMT",
+ "Content-Type": "application/activity+json; charset=utf-8",
+ "Transfer-Encoding": "chunked",
+ "Connection": "keep-alive",
+ "Server": "Mastodon",
+ "X-Frame-Options": "DENY",
+ "X-Content-Type-Options": "nosniff",
+ "X-XSS-Protection": "1; mode=block",
+ "Vary": "Accept, Accept-Encoding, Origin",
+ "Cache-Control": "max-age=180, public",
+ "ETag": "W/\"11665b12333a8c7708de7b17f58147b2\"",
+ "Set-Cookie": "_mastodon_session=hkR%2BepdH7Hnl0wgxjtkiOa6%2FY8%2FIs4lElyGl%2FRMoRqdztBigOMH19196k1gDXNqqotlhjZMGBcDPv5tSOTdN--UzxEtxF4SS5Vwfhn--S3FqvLDMaBYDpE2P4o64Nw%3D%3D; path=/; secure; HttpOnly",
+ "X-Request-Id": "88981ba1-9aa8-428c-a868-78a918cf1317",
+ "X-Runtime": "0.013394",
+ "X-Cached": "MISS",
+ "Strict-Transport-Security": "max-age=31536000"
+ },
+ "status_code": 200,
+ "type": "ok"
+ }
}
]
\ No newline at end of file
diff --git a/test/fixtures/vcr_cassettes/activity_pub/object_bogus_origin.json b/test/fixtures/vcr_cassettes/activity_pub/object_bogus_origin.json
new file mode 100644
index 000000000..88f08bbb7
--- /dev/null
+++ b/test/fixtures/vcr_cassettes/activity_pub/object_bogus_origin.json
@@ -0,0 +1,78 @@
+[
+ {
+ "request": {
+ "body": "",
+ "headers": {
+ "Accept": "application/activity+json",
+ "Date": "Sun, 15 Dec 2019 20:24:06 GMT",
+ "Signature": "keyId=\"http://mobilizon.test/relay#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) date host\",signature=\"kUYhmOSdg7kxApgTCEj1CZLwOGn31sLbPdU35xu6Y5f/I3oT65g6KYlzuvchfQTrJ0XTzdzdSpQ5ZC/Y5dQEZrc3yzGw0KOmpSFfDcbtLw0I4Ya3vRjvuw0cDsPUwxoKpW1FqkMIEXP2lTXV/Bywc2rWCytnttiJwoQdeTvPDigmCCLxo2+wNMshl169HjAjYT9T8O0ptlgZXZ+JPuuaMj6EcSnXJDAkDxdBo54D61ED+dIIDxRKJsCTDrnjvZ86E9Z/P8SIbQxPZNw+TpLeofFTi/xt0E42M76iOEk41+kKlWQBd4imwoJesYEU+7CsMRtttt7wK9hMqcQC4UCa8g==\""
+ },
+ "method": "get",
+ "options": {
+ "follow_redirect": "true",
+ "recv_timeout": 20000,
+ "connect_timeout": 10000
+ },
+ "request_body": "",
+ "url": "https://info.pleroma.site/activity.json"
+ },
+ "response": {
+ "binary": false,
+ "body": "{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"actor\": \"https://queer.hacktivis.me/users/lanodan\",\n \"announcement_count\": 3,\n \"announcements\": [\n \"https://io.markegli.com/users/mark\",\n \"https://voluntaryism.club/users/sevvie\",\n \"https://pleroma.pla1.net/users/pla\"\n ],\n \"attachment\": [],\n \"attributedTo\": \"https://queer.hacktivis.me/users/lanodan\",\n \"content\": \"this post was not actually written by Haelwenn \",\n \"id\": \"https://info.pleroma.site/activity.json\",\n \"published\": \"2018-09-01T22:15:00Z\",\n \"tag\": [],\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\"\n ],\n \"type\": \"Note\"\n}\n",
+ "headers": {
+ "Server": "nginx",
+ "Date": "Sun, 15 Dec 2019 20:24:07 GMT",
+ "Content-Type": "application/json",
+ "Content-Length": "750",
+ "Last-Modified": "Sat, 01 Sep 2018 22:56:24 GMT",
+ "Connection": "keep-alive",
+ "ETag": "\"5b8b1918-2ee\"",
+ "Accept-Ranges": "bytes"
+ },
+ "status_code": 200,
+ "type": "ok"
+ }
+ },
+ {
+ "request": {
+ "body": "",
+ "headers": {
+ "Accept": "application/activity+json"
+ },
+ "method": "get",
+ "options": {
+ "follow_redirect": "true"
+ },
+ "request_body": "",
+ "url": "https://queer.hacktivis.me/users/lanodan"
+ },
+ "response": {
+ "binary": false,
+ "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://queer.hacktivis.me/schemas/litepub-0.1.jsonld\",{\"@language\":\"und\"}],\"attachment\":[],\"discoverable\":false,\"endpoints\":{\"oauthAuthorizationEndpoint\":\"https://queer.hacktivis.me/oauth/authorize\",\"oauthRegistrationEndpoint\":\"https://queer.hacktivis.me/api/v1/apps\",\"oauthTokenEndpoint\":\"https://queer.hacktivis.me/oauth/token\",\"sharedInbox\":\"https://queer.hacktivis.me/inbox\",\"uploadMedia\":\"https://queer.hacktivis.me/api/ap/upload_media\"},\"followers\":\"https://queer.hacktivis.me/users/lanodan/followers\",\"following\":\"https://queer.hacktivis.me/users/lanodan/following\",\"icon\":{\"type\":\"Image\",\"url\":\"https://queer.hacktivis.me/media/c8e81887-7d81-4cdc-91b3-c624ea79e6c9/425f089961270eff91b66d45f8faeeb12a725a5f87a6a52bfc54c43bd89f5fe9.png\"},\"id\":\"https://queer.hacktivis.me/users/lanodan\",\"image\":{\"type\":\"Image\",\"url\":\"https://queer.hacktivis.me/media/37b6ce56-8c24-4e64-bd70-a76e84ab0c69/53a48a3a49ed5e5637a84e4f3663df17f8d764244bbc1027ba03cfc446e8b7bd.jpg\"},\"inbox\":\"https://queer.hacktivis.me/users/lanodan/inbox\",\"manuallyApprovesFollowers\":true,\"name\":\"Haelwenn /ɛlwən/ 🐺\",\"outbox\":\"https://queer.hacktivis.me/users/lanodan/outbox\",\"preferredUsername\":\"lanodan\",\"publicKey\":{\"id\":\"https://queer.hacktivis.me/users/lanodan#main-key\",\"owner\":\"https://queer.hacktivis.me/users/lanodan\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsWOgdjSMc010qvxC3njI\\nXJlFWMJ5gJ8QXCW/PajYdsHPM6d+jxBNJ6zp9/tIRa2m7bWHTSkuHQ7QthOpt6vu\\n+dAWpKRLS607SPLItn/qUcyXvgN+H8shfyhMxvkVs9jXdtlBsLUVE7UNpN0dxzqe\\nI79QWbf7o4amgaIWGRYB+OYMnIxKt+GzIkivZdSVSYjfxNnBYkMCeUxm5EpPIxKS\\nP5bBHAVRRambD5NUmyKILuC60/rYuc/C+vmgpY2HCWFS2q6o34dPr9enwL6t4b3m\\nS1t/EJHk9rGaaDqSGkDEfyQI83/7SDebWKuETMKKFLZi1vMgQIFuOYCIhN6bIiZm\\npQIDAQAB\\n-----END PUBLIC KEY-----\\n\\n\"},\"summary\":\"--- Website: https://hacktivis.me/ Pronouns: she/fae, elle/iel Lang: en, fr, (LSF), ... ``` 🦊🦄⚧🂡ⓥ :anarchy: 👿🐧 :gentoo: Pleroma dev (backend, mastofe)
banner from: https://soc.flyingcube.tech/objects/56f79be2-9013-4559-9826-f7dc392417db Federation-bots: #nobot\",\"tag\":[{\"icon\":{\"type\":\"Image\",\"url\":\"https://queer.hacktivis.me/emoji/custom/symbols/anarchy.png\"},\"name\":\":anarchy:\",\"type\":\"Emoji\"},{\"icon\":{\"type\":\"Image\",\"url\":\"https://queer.hacktivis.me/emoji/custom/gentoo.png\"},\"name\":\":gentoo:\",\"type\":\"Emoji\"}],\"type\":\"Person\",\"url\":\"https://queer.hacktivis.me/users/lanodan\"}",
+ "headers": {
+ "Server": "nginx/1.16.1",
+ "Date": "Sun, 15 Dec 2019 20:24:07 GMT",
+ "Content-Type": "application/activity+json; charset=utf-8",
+ "Content-Length": "2693",
+ "Connection": "keep-alive",
+ "Keep-Alive": "timeout=20",
+ "access-control-allow-credentials": "true",
+ "access-control-allow-origin": "*",
+ "access-control-expose-headers": "Link,X-RateLimit-Reset,X-RateLimit-Limit,X-RateLimit-Remaining,X-Request-Id,Idempotency-Key",
+ "cache-control": "max-age=0, private, must-revalidate",
+ "content-security-policy": "default-src 'none'; base-uri 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; manifest-src 'self'; connect-src 'self' https://queer.hacktivis.me wss://queer.hacktivis.me; script-src 'self'; upgrade-insecure-requests;",
+ "expect-ct": "enforce, max-age=2592000",
+ "referrer-policy": "no-referrer",
+ "strict-transport-security": "max-age=31536000; includeSubDomains",
+ "x-content-type-options": "nosniff",
+ "x-download-options": "noopen",
+ "x-frame-options": "DENY",
+ "x-permitted-cross-domain-policies": "none",
+ "x-request-id": "FeClJPRE1U5AcP8ABtkB",
+ "x-xss-protection": "1; mode=block"
+ },
+ "status_code": 200,
+ "type": "ok"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/fixtures/vcr_cassettes/activity_pub/signature/invalid_not_found.json b/test/fixtures/vcr_cassettes/activity_pub/signature/invalid_not_found.json
new file mode 100644
index 000000000..194a42c68
--- /dev/null
+++ b/test/fixtures/vcr_cassettes/activity_pub/signature/invalid_not_found.json
@@ -0,0 +1,39 @@
+[
+ {
+ "request": {
+ "body": "",
+ "headers": {
+ "Accept": "application/activity+json"
+ },
+ "method": "get",
+ "options": {
+ "follow_redirect": "true"
+ },
+ "request_body": "",
+ "url": "http://niu.moe/users/rye"
+ },
+ "response": {
+ "binary": false,
+ "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"toot\":\"http://joinmastodon.org/ns#\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"alsoKnownAs\":{\"@id\":\"as:alsoKnownAs\",\"@type\":\"@id\"},\"movedTo\":{\"@id\":\"as:movedTo\",\"@type\":\"@id\"},\"schema\":\"http://schema.org#\",\"PropertyValue\":\"schema:PropertyValue\",\"value\":\"schema:value\",\"IdentityProof\":\"toot:IdentityProof\",\"discoverable\":\"toot:discoverable\",\"focalPoint\":{\"@container\":\"@list\",\"@id\":\"toot:focalPoint\"}}],\"id\":\"https://niu.moe/users/rye\",\"type\":\"Person\",\"following\":\"https://niu.moe/users/rye/following\",\"followers\":\"https://niu.moe/users/rye/followers\",\"inbox\":\"https://niu.moe/users/rye/inbox\",\"outbox\":\"https://niu.moe/users/rye/outbox\",\"featured\":\"https://niu.moe/users/rye/collections/featured\",\"preferredUsername\":\"rye\",\"name\":\"♡ rye ♡\",\"summary\":\"\\u003cp\\u003eicon from \\u003ca href=\\\"https://twitter.com/_nitronic/status/1137776178687725568\\\" rel=\\\"nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"ellipsis\\\"\\u003etwitter.com/_nitronic/status/1\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e137776178687725568\\u003c/span\\u003e\\u003c/a\\u003e くコ:彡\\u003c/p\\u003e\\u003cp\\u003eCome back with a warrant\\u003c/p\\u003e\",\"url\":\"https://niu.moe/@rye\",\"manuallyApprovesFollowers\":false,\"discoverable\":false,\"publicKey\":{\"id\":\"https://niu.moe/users/rye#main-key\",\"owner\":\"https://niu.moe/users/rye\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA83uRWjCFO35FwfA38mzv\\nEL0TUaXB7+2hYvPwNrn1WY6me5DRbqB5zzMrzWMGr0HSooqNqEYBafGsmVTWUqIk\\nKM9ehtIBraJI+mT5X7DPR3LrXOJF4a9EEslg8XvAk8MN9IrAhm6UljnvB67RtDcA\\nTNB01VWy9yWnxFRtz9o/EMoBPyw5giOaXE2ibVNP8lQIqGKuuBKPzPjSJygdvQ5q\\nxfow2z1TpKRqdsNDqn4n6U6zCXYTzkr0J71/tGw7fsgfv78l0Wjrc7EcuBk74OaG\\nC65UDiu3X4Q6kxCfCEhPSfuwLN+UZkzxcn6goWR0iYpWs57+4tFKu9nJYP4QJ0K9\\nTwIDAQAB\\n-----END PUBLIC KEY-----\\n\"},\"tag\":[],\"attachment\":[],\"endpoints\":{\"sharedInbox\":\"https://niu.moe/inbox\"},\"icon\":{\"type\":\"Image\",\"mediaType\":\"image/png\",\"url\":\"https://cdn.niu.moe/accounts/avatars/000/033/323/original/e4d637b2c8755a7e.png\"},\"image\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://cdn.niu.moe/accounts/headers/000/033/323/original/cc89e1bc66b99a65.jpeg\"}}",
+ "headers": {
+ "Cache-Control": "max-age=180, public",
+ "Content-Type": "application/activity+json; charset=utf-8",
+ "Date": "Fri, 06 Dec 2019 10:26:01 GMT",
+ "Etag": "W/\"b3c2a8220a20671e8ca5c7e3371e7f5a\"",
+ "Server": "Caddy",
+ "Set-Cookie": "_mastodon_session=lSwFzD6GF6%2FjEL7hWLHU61n7%2B8kC60xvZYlPZG8EBtcndPzfd2%2B976zDOf1ALGZkvqj3CdpYHbZyq%2B7cwfkX--Ut%2BKGA8YibOTCEhb--a0sE5cHGI5PicAmO2yDlZw%3D%3D; path=/; secure; HttpOnly",
+ "Strict-Transport-Security": "max-age=31536000",
+ "Vary": "Accept, Accept-Encoding, Origin",
+ "X-Cached": "MISS",
+ "X-Content-Type-Options": "nosniff",
+ "X-Frame-Options": "DENY",
+ "X-Request-Id": "0cc18bab-2d72-47b2-8778-8646402e1148",
+ "X-Runtime": "0.009213",
+ "X-Xss-Protection": "1; mode=block",
+ "Transfer-Encoding": "chunked"
+ },
+ "status_code": 200,
+ "type": "ok"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/fixtures/vcr_cassettes/activity_pub/signature/invalid_payload.json b/test/fixtures/vcr_cassettes/activity_pub/signature/invalid_payload.json
new file mode 100644
index 000000000..f150c2360
--- /dev/null
+++ b/test/fixtures/vcr_cassettes/activity_pub/signature/invalid_payload.json
@@ -0,0 +1,39 @@
+[
+ {
+ "request": {
+ "body": "",
+ "headers": {
+ "Accept": "application/activity+json"
+ },
+ "method": "get",
+ "options": {
+ "follow_redirect": "true"
+ },
+ "request_body": "",
+ "url": "https://niu.moe/users/rye"
+ },
+ "response": {
+ "binary": false,
+ "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"toot\":\"http://joinmastodon.org/ns#\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"alsoKnownAs\":{\"@id\":\"as:alsoKnownAs\",\"@type\":\"@id\"},\"movedTo\":{\"@id\":\"as:movedTo\",\"@type\":\"@id\"},\"schema\":\"http://schema.org#\",\"PropertyValue\":\"schema:PropertyValue\",\"value\":\"schema:value\",\"IdentityProof\":\"toot:IdentityProof\",\"discoverable\":\"toot:discoverable\",\"focalPoint\":{\"@container\":\"@list\",\"@id\":\"toot:focalPoint\"}}],\"id\":\"https://niu.moe/users/rye\",\"type\":\"Person\",\"following\":\"https://niu.moe/users/rye/following\",\"followers\":\"https://niu.moe/users/rye/followers\",\"inbox\":\"https://niu.moe/users/rye/inbox\",\"outbox\":\"https://niu.moe/users/rye/outbox\",\"featured\":\"https://niu.moe/users/rye/collections/featured\",\"preferredUsername\":\"rye\",\"name\":\"♡ rye ♡\",\"summary\":\"\\u003cp\\u003eicon from \\u003ca href=\\\"https://twitter.com/_nitronic/status/1137776178687725568\\\" rel=\\\"nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"ellipsis\\\"\\u003etwitter.com/_nitronic/status/1\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e137776178687725568\\u003c/span\\u003e\\u003c/a\\u003e くコ:彡\\u003c/p\\u003e\\u003cp\\u003eCome back with a warrant\\u003c/p\\u003e\",\"url\":\"https://niu.moe/@rye\",\"manuallyApprovesFollowers\":false,\"discoverable\":false,\"publicKey\":{\"id\":\"https://niu.moe/users/rye#main-key\",\"owner\":\"https://niu.moe/users/rye\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA83uRWjCFO35FwfA38mzv\\nEL0TUaXB7+2hYvPwNrn1WY6me5DRbqB5zzMrzWMGr0HSooqNqEYBafGsmVTWUqIk\\nKM9ehtIBraJI+mT5X7DPR3LrXOJF4a9EEslg8XvAk8MN9IrAhm6UljnvB67RtDcA\\nTNB01VWy9yWnxFRtz9o/EMoBPyw5giOaXE2ibVNP8lQIqGKuuBKPzPjSJygdvQ5q\\nxfow2z1TpKRqdsNDqn4n6U6zCXYTzkr0J71/tGw7fsgfv78l0Wjrc7EcuBk74OaG\\nC65UDiu3X4Q6kxCfCEhPSfuwLN+UZkzxcn6goWR0iYpWs57+4tFKu9nJYP4QJ0K9\\nTwIDAQAB\\n-----END PUBLIC KEY-----\\n\"},\"tag\":[],\"attachment\":[],\"endpoints\":{\"sharedInbox\":\"https://niu.moe/inbox\"},\"icon\":{\"type\":\"Image\",\"mediaType\":\"image/png\",\"url\":\"https://cdn.niu.moe/accounts/avatars/000/033/323/original/e4d637b2c8755a7e.png\"},\"image\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://cdn.niu.moe/accounts/headers/000/033/323/original/cc89e1bc66b99a65.jpeg\"}}",
+ "headers": {
+ "Cache-Control": "max-age=180, public",
+ "Content-Type": "application/activity+json; charset=utf-8",
+ "Date": "Fri, 06 Dec 2019 10:26:00 GMT",
+ "Etag": "W/\"06011deced02514fa9adc61bf61ed2fd\"",
+ "Server": "Caddy",
+ "Set-Cookie": "_mastodon_session=n67ChnKe59aqgHL9yq2ReOf2DXDK7c54n49moftpN5s3c6AJpyJ9QZUH31wz0eyDiSiHHw4A6IgzpkrhvSF0--Gm%2FvZr27eWvDBh23--BROo3uKkLhfRDQgszEDO3w%3D%3D; path=/; secure; HttpOnly",
+ "Strict-Transport-Security": "max-age=31536000",
+ "Vary": "Accept, Accept-Encoding, Origin",
+ "X-Cached": "MISS",
+ "X-Content-Type-Options": "nosniff",
+ "X-Frame-Options": "DENY",
+ "X-Request-Id": "acee93a9-4da7-4495-bf03-ae7113c50b43",
+ "X-Runtime": "0.016198",
+ "X-Xss-Protection": "1; mode=block",
+ "Transfer-Encoding": "chunked"
+ },
+ "status_code": 200,
+ "type": "ok"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/fixtures/vcr_cassettes/activity_pub/signature/valid.json b/test/fixtures/vcr_cassettes/activity_pub/signature/valid.json
new file mode 100644
index 000000000..702cccddf
--- /dev/null
+++ b/test/fixtures/vcr_cassettes/activity_pub/signature/valid.json
@@ -0,0 +1,40 @@
+[
+ {
+ "request": {
+ "body": "",
+ "headers": {
+ "Accept": "application/activity+json"
+ },
+ "method": "get",
+ "options": {
+ "follow_redirect": "true"
+ },
+ "request_body": "",
+ "url": "https://framapiaf.org/users/admin"
+ },
+ "response": {
+ "binary": false,
+ "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"toot\":\"http://joinmastodon.org/ns#\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"alsoKnownAs\":{\"@id\":\"as:alsoKnownAs\",\"@type\":\"@id\"},\"movedTo\":{\"@id\":\"as:movedTo\",\"@type\":\"@id\"},\"schema\":\"http://schema.org#\",\"PropertyValue\":\"schema:PropertyValue\",\"value\":\"schema:value\",\"IdentityProof\":\"toot:IdentityProof\",\"discoverable\":\"toot:discoverable\",\"focalPoint\":{\"@container\":\"@list\",\"@id\":\"toot:focalPoint\"}}],\"id\":\"https://framapiaf.org/users/admin\",\"type\":\"Service\",\"following\":\"https://framapiaf.org/users/admin/following\",\"followers\":\"https://framapiaf.org/users/admin/followers\",\"inbox\":\"https://framapiaf.org/users/admin/inbox\",\"outbox\":\"https://framapiaf.org/users/admin/outbox\",\"featured\":\"https://framapiaf.org/users/admin/collections/featured\",\"preferredUsername\":\"admin\",\"name\":\"Administrateur\",\"summary\":\"\\u003cp\\u003eJe ne suis qu\\u0026apos;un compte inutile. Merci nous de contacter via \\u003ca href=\\\"https://contact.framasoft.org/\\\" rel=\\\"nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003econtact.framasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/p\\u003e\",\"url\":\"https://framapiaf.org/@admin\",\"manuallyApprovesFollowers\":false,\"discoverable\":null,\"publicKey\":{\"id\":\"https://framapiaf.org/users/admin#main-key\",\"owner\":\"https://framapiaf.org/users/admin\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyHaU/AZ5dWtSxZXkPa89\\nDUQ4z+JQHGGUG/xkGuq0v8P6qJfQqtHPBO5vH0IQJqluXWQS96gqTwjZnYevcpNA\\nveYv0K25DWszx5Ehz6JX2/sSvu2rNUcQ3YZvSjdo/Yy1u5Fuc5lLmvw8uFzXYekD\\nWovTMOnp4mIKpVEm/G/v4w8jvFEKw88h743vwaEIim88GEQItMxzGAV6zSqV1DWO\\nLxtoRsinslJYfAG46ex4YUATFveWvOUeWk5W1sEa5f3c0moaTmBM/PAAo8vLxhlw\\nJhsHihsCH+BcXKVMjW8OCqYYqISMxEifUBX63HcJt78ELHpOuc1c2eG59PomtTjQ\\nywIDAQAB\\n-----END PUBLIC KEY-----\\n\"},\"tag\":[],\"attachment\":[{\"type\":\"PropertyValue\",\"name\":\"News\",\"value\":\"\\u003cspan class=\\\"h-card\\\"\\u003e\\u003ca href=\\\"https://framapiaf.org/@Framasoft\\\" class=\\\"u-url mention\\\"\\u003e@\\u003cspan\\u003eFramasoft\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/span\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Support\",\"value\":\"\\u003ca href=\\\"https://contact.framasoft.org/\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003econtact.framasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Soutenir\",\"value\":\"\\u003ca href=\\\"https://soutenir.framasoft.org/\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003esoutenir.framasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Site\",\"value\":\"\\u003ca href=\\\"https://framasoft.org/\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003eframasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"}],\"endpoints\":{\"sharedInbox\":\"https://framapiaf.org/inbox\"},\"icon\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/avatars/000/000/002/original/85fbb27ad5e3cf71.jpg\"},\"image\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/headers/000/000/002/original/6aba75f1ab1ab6de.jpg\"}}",
+ "headers": {
+ "Date": "Fri, 06 Dec 2019 10:25:59 GMT",
+ "Content-Type": "application/activity+json; charset=utf-8",
+ "Transfer-Encoding": "chunked",
+ "Connection": "keep-alive",
+ "Server": "Mastodon",
+ "X-Frame-Options": "DENY",
+ "X-Content-Type-Options": "nosniff",
+ "X-XSS-Protection": "1; mode=block",
+ "Vary": "Accept, Accept-Encoding, Origin",
+ "Cache-Control": "max-age=180, public",
+ "ETag": "W/\"773e09a2a60446fe74d997858877f7e0\"",
+ "Set-Cookie": "_mastodon_session=2qQRsid4lIe3JZJLG3ZxxfaDP4XDPsKEtqr9Bf3tljCQUEYrQZtQ44k74K1S1VeO3d2O2ztK7eafBlOx0KGQ--FK4A%2Bp1X4tvqnhba--KDpnaRQfBtiHOWhqW7ECEg%3D%3D; path=/; secure; HttpOnly",
+ "X-Request-Id": "c70d5224-5f9a-481a-a6d0-b817b0054be9",
+ "X-Runtime": "0.004654",
+ "X-Cached": "MISS",
+ "Strict-Transport-Security": "max-age=31536000"
+ },
+ "status_code": 200,
+ "type": "ok"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/fixtures/vcr_cassettes/activity_pub/signature/valid_payload.json b/test/fixtures/vcr_cassettes/activity_pub/signature/valid_payload.json
new file mode 100644
index 000000000..6bbc7ffa2
--- /dev/null
+++ b/test/fixtures/vcr_cassettes/activity_pub/signature/valid_payload.json
@@ -0,0 +1,40 @@
+[
+ {
+ "request": {
+ "body": "",
+ "headers": {
+ "Accept": "application/activity+json"
+ },
+ "method": "get",
+ "options": {
+ "follow_redirect": "true"
+ },
+ "request_body": "",
+ "url": "https://framapiaf.org/users/admin"
+ },
+ "response": {
+ "binary": false,
+ "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"toot\":\"http://joinmastodon.org/ns#\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"alsoKnownAs\":{\"@id\":\"as:alsoKnownAs\",\"@type\":\"@id\"},\"movedTo\":{\"@id\":\"as:movedTo\",\"@type\":\"@id\"},\"schema\":\"http://schema.org#\",\"PropertyValue\":\"schema:PropertyValue\",\"value\":\"schema:value\",\"IdentityProof\":\"toot:IdentityProof\",\"discoverable\":\"toot:discoverable\",\"focalPoint\":{\"@container\":\"@list\",\"@id\":\"toot:focalPoint\"}}],\"id\":\"https://framapiaf.org/users/admin\",\"type\":\"Service\",\"following\":\"https://framapiaf.org/users/admin/following\",\"followers\":\"https://framapiaf.org/users/admin/followers\",\"inbox\":\"https://framapiaf.org/users/admin/inbox\",\"outbox\":\"https://framapiaf.org/users/admin/outbox\",\"featured\":\"https://framapiaf.org/users/admin/collections/featured\",\"preferredUsername\":\"admin\",\"name\":\"Administrateur\",\"summary\":\"\\u003cp\\u003eJe ne suis qu\\u0026apos;un compte inutile. Merci nous de contacter via \\u003ca href=\\\"https://contact.framasoft.org/\\\" rel=\\\"nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003econtact.framasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/p\\u003e\",\"url\":\"https://framapiaf.org/@admin\",\"manuallyApprovesFollowers\":false,\"discoverable\":null,\"publicKey\":{\"id\":\"https://framapiaf.org/users/admin#main-key\",\"owner\":\"https://framapiaf.org/users/admin\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyHaU/AZ5dWtSxZXkPa89\\nDUQ4z+JQHGGUG/xkGuq0v8P6qJfQqtHPBO5vH0IQJqluXWQS96gqTwjZnYevcpNA\\nveYv0K25DWszx5Ehz6JX2/sSvu2rNUcQ3YZvSjdo/Yy1u5Fuc5lLmvw8uFzXYekD\\nWovTMOnp4mIKpVEm/G/v4w8jvFEKw88h743vwaEIim88GEQItMxzGAV6zSqV1DWO\\nLxtoRsinslJYfAG46ex4YUATFveWvOUeWk5W1sEa5f3c0moaTmBM/PAAo8vLxhlw\\nJhsHihsCH+BcXKVMjW8OCqYYqISMxEifUBX63HcJt78ELHpOuc1c2eG59PomtTjQ\\nywIDAQAB\\n-----END PUBLIC KEY-----\\n\"},\"tag\":[],\"attachment\":[{\"type\":\"PropertyValue\",\"name\":\"News\",\"value\":\"\\u003cspan class=\\\"h-card\\\"\\u003e\\u003ca href=\\\"https://framapiaf.org/@Framasoft\\\" class=\\\"u-url mention\\\"\\u003e@\\u003cspan\\u003eFramasoft\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/span\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Support\",\"value\":\"\\u003ca href=\\\"https://contact.framasoft.org/\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003econtact.framasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Soutenir\",\"value\":\"\\u003ca href=\\\"https://soutenir.framasoft.org/\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003esoutenir.framasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"},{\"type\":\"PropertyValue\",\"name\":\"Site\",\"value\":\"\\u003ca href=\\\"https://framasoft.org/\\\" rel=\\\"me nofollow noopener\\\" target=\\\"_blank\\\"\\u003e\\u003cspan class=\\\"invisible\\\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\\\"\\\"\\u003eframasoft.org/\\u003c/span\\u003e\\u003cspan class=\\\"invisible\\\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e\"}],\"endpoints\":{\"sharedInbox\":\"https://framapiaf.org/inbox\"},\"icon\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/avatars/000/000/002/original/85fbb27ad5e3cf71.jpg\"},\"image\":{\"type\":\"Image\",\"mediaType\":\"image/jpeg\",\"url\":\"https://framapiaf.s3.framasoft.org/framapiaf/accounts/headers/000/000/002/original/6aba75f1ab1ab6de.jpg\"}}",
+ "headers": {
+ "Date": "Fri, 06 Dec 2019 10:25:59 GMT",
+ "Content-Type": "application/activity+json; charset=utf-8",
+ "Transfer-Encoding": "chunked",
+ "Connection": "keep-alive",
+ "Server": "Mastodon",
+ "X-Frame-Options": "DENY",
+ "X-Content-Type-Options": "nosniff",
+ "X-XSS-Protection": "1; mode=block",
+ "Vary": "Accept, Accept-Encoding, Origin",
+ "Cache-Control": "max-age=180, public",
+ "ETag": "W/\"773e09a2a60446fe74d997858877f7e0\"",
+ "Set-Cookie": "_mastodon_session=U%2BUwfKRPF9LVzxKAjxaywz3ySEufApGuEhddpwvpJm7%2B0cDzsW0%2Fn64%2FwOLOYuE9SLOTCjU4Ufc3yaoLvdKx--x1HVRQfU7bAeHgaF--IV9oyi7ODNo19cAi%2FULzew%3D%3D; path=/; secure; HttpOnly",
+ "X-Request-Id": "f36578b7-2f1a-49d1-b2c4-0cc0d652160b",
+ "X-Runtime": "0.010705",
+ "X-Cached": "MISS",
+ "Strict-Transport-Security": "max-age=31536000"
+ },
+ "status_code": 200,
+ "type": "ok"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/fixtures/vcr_cassettes/relay/fetch_relay_follow.json b/test/fixtures/vcr_cassettes/relay/fetch_relay_follow.json
index 4f911b258..b477ec249 100644
--- a/test/fixtures/vcr_cassettes/relay/fetch_relay_follow.json
+++ b/test/fixtures/vcr_cassettes/relay/fetch_relay_follow.json
@@ -1,4 +1,36 @@
[
+ {
+ "request": {
+ "body": "",
+ "headers": {
+ "Accept": "application/json, application/activity+json, application/jrd+json"
+ },
+ "method": "get",
+ "options": {
+ "follow_redirect": "true"
+ },
+ "request_body": "",
+ "url": "http://mobilizon1.com/.well-known/webfinger?resource=acct:relay@mobilizon1.com"
+ },
+ "response": {
+ "binary": false,
+ "body": "{\"aliases\":[\"http://mobilizon1.com/relay\"],\"links\":[{\"href\":\"http://mobilizon1.com/relay\",\"rel\":\"self\",\"type\":\"application/activity+json\"},{\"href\":\"http://mobilizon1.com/relay\",\"rel\":\"https://webfinger.net/rel/profile-page/\",\"type\":\"text/html\"}],\"subject\":\"acct:relay@mobilizon1.com\"}",
+ "headers": {
+ "Server": "nginx/1.16.1",
+ "Date": "Fri, 13 Dec 2019 09:41:40 GMT",
+ "Content-Type": "application/json; charset=utf-8",
+ "Content-Length": "284",
+ "Connection": "keep-alive",
+ "access-control-allow-credentials": "true",
+ "access-control-allow-origin": "*",
+ "access-control-expose-headers": "",
+ "cache-control": "max-age=0, private, must-revalidate",
+ "x-request-id": "Fd_k7OtPJ28p8-MAAAOh"
+ },
+ "status_code": 200,
+ "type": "ok"
+ }
+ },
{
"request": {
"body": "",
@@ -10,16 +42,22 @@
"follow_redirect": "true"
},
"request_body": "",
- "url": "http://localhost:8080/actor"
+ "url": "http://mobilizon1.com/relay"
},
"response": {
"binary": false,
- "body": "{\"@context\": \"https://www.w3.org/ns/activitystreams\", \"endpoints\": {\"sharedInbox\": \"http://localhost:8080/inbox\"}, \"followers\": \"http://localhost:8080/followers\", \"following\": \"http://localhost:8080/following\", \"inbox\": \"http://localhost:8080/inbox\", \"name\": \"ActivityRelay\", \"type\": \"Application\", \"id\": \"http://localhost:8080/actor\", \"publicKey\": {\"id\": \"http://localhost:8080/actor#main-key\", \"owner\": \"http://localhost:8080/actor\", \"publicKeyPem\": \"-----BEGIN PUBLIC KEY-----\\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvs6UuAo26Sb3BiOK7xay\\nsBzqvXI3xd55JAP0pAk2faF+Vl3r67/g9MoND96JqCVMuzSJZ9oSsqa6ilJCxG3p\\nXUUfQUvqAMGW49cCvga86DG17Ennjbc4C6WIQtoW3Wm5OdDciPY2Dx+pSXdTOajB\\nFX6RHUZcgqHENrsm3jPZI138e/2OJeqdxv4/5t2xdPXEpWdPGitX9AJhrqPY4lzg\\nzQ9Y9wS2eS1CVL9vZZRf9Z4RiZvAfVb0s1iS/IUxrf4TYERRFJxEoDLD2SZVrkq6\\nvhGldCfw2ZnfTftA1ToXguC9S6nSaz+li0ajNjpK/xjZjlKvn0I078UPPe5LUlsb\\nUcYZvBx5PC5rV8yKMLlgxnTY8PqC8LEVc453wO7Ai4M5TeB0SUyEycZHSyLfvQXV\\nThEN/07u1UaJViY3U5S/SihyoCQUfJXQ3jx2SjGgM32/aJ3IwxgveLaTsaZ0VVKM\\nbawEFw6iAcWYM06hZSB6j6dkL1xh+FYGEQTPMYMqUOJi2r1cD8yMLe8dTFOmwMLt\\nBnf7xxvnjKJcv3e9zGRWIdLkQbBQn3BEuRTCUMgljipxdjbeE5/JSP1kQLB94ncb\\nb9gvYgtemJKvT8m37+HOi9MI4BMIlDwpRWjqPZmkNvkegR/1KPjJSsyAnGdd89ne\\np442vUqPyXIq0tSCDmjmU+cCAwEAAQ==\\n-----END PUBLIC KEY-----\"}, \"summary\": \"ActivityRelay bot\", \"preferredUsername\": \"relay\", \"url\": \"http://localhost:8080/actor\"}",
+ "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://litepub.social/litepub/context.jsonld\",{\"Hashtag\":\"as:Hashtag\",\"category\":\"sc:category\",\"ical\":\"http://www.w3.org/2002/12/cal/ical#\",\"joinMode\":{\"@id\":\"mz:joinMode\",\"@type\":\"mz:joinModeType\"},\"joinModeType\":{\"@id\":\"mz:joinModeType\",\"@type\":\"rdfs:Class\"},\"maximumAttendeeCapacity\":\"sc:maximumAttendeeCapacity\",\"mz\":\"https://joinmobilizon.org/ns#\",\"repliesModerationOption\":{\"@id\":\"mz:repliesModerationOption\",\"@type\":\"mz:repliesModerationOptionType\"},\"repliesModerationOptionType\":{\"@id\":\"mz:repliesModerationOptionType\",\"@type\":\"rdfs:Class\"},\"sc\":\"http://schema.org#\",\"uuid\":\"sc:identifier\"}],\"endpoints\":{\"sharedInbox\":\"http://mobilizon1.com/inbox\"},\"followers\":\"http://mobilizon1.com/relay/followers\",\"following\":\"http://mobilizon1.com/relay/following\",\"id\":\"http://mobilizon1.com/relay\",\"inbox\":\"http://mobilizon1.com/inbox\",\"manuallyApprovesFollowers\":false,\"name\":\"Mobilizon\",\"outbox\":null,\"preferredUsername\":\"relay\",\"publicKey\":{\"id\":\"http://mobilizon1.com/relay#main-key\",\"owner\":\"http://mobilizon1.com/relay\",\"publicKeyPem\":\"-----BEGIN RSA PUBLIC KEY-----\\nMIIBCgKCAQEAqBbeHMV5UVw0AIVch7fWDp2it5rqbGZX6yXPYnnT8LHhdvfv3DFk\\npk74BN66MzNqsthvSVznu2BEil0sEKD5rQoE9Yirhzz/LN9SlnU+u6262nBA18E3\\nkQ10RgL2jpZ9e8Om6qYqarhN7draupJXYRKEaUoEFPT09ABbwQv+4K1YadU8klJi\\nHJ6D+IIHiXNizfsxVLDKpbUKStMYeEzyfqCkWw0EQEuzc3O7Aci5lwCMkCts2993\\nsTbNyzsYAVWJNcy/An1F1P+K4iZhWEtZInQz67MBtjMWtQUhyWib0e671HdBiWM6\\nkZq74U8c6RR6eMzBLuY7YAUCG6nWg90zxwIDAQAB\\n-----END RSA PUBLIC KEY-----\\n\\n\"},\"summary\":\"Change this to a proper description of your instance\",\"type\":\"Application\",\"url\":\"http://mobilizon1.com/relay\"}",
"headers": {
- "Content-Type": "application/json; charset=utf-8",
- "Content-Length": "1368",
- "Date": "Thu, 01 Aug 2019 14:44:38 GMT",
- "Server": "Python/3.7 aiohttp/3.3.2"
+ "Server": "nginx/1.16.1",
+ "Date": "Fri, 13 Dec 2019 09:41:41 GMT",
+ "Content-Type": "application/activity+json",
+ "Content-Length": "1657",
+ "Connection": "keep-alive",
+ "access-control-allow-credentials": "true",
+ "access-control-allow-origin": "*",
+ "access-control-expose-headers": "",
+ "cache-control": "max-age=0, private, must-revalidate",
+ "x-request-id": "Fd_k7PjOAWuySL0AAAPB"
},
"status_code": 200,
"type": "ok"
@@ -27,30 +65,36 @@
},
{
"request": {
- "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://litepub.github.io/litepub/context.jsonld\",{\"Hashtag\":\"as:Hashtag\",\"category\":\"sc:category\",\"sc\":\"http://schema.org#\",\"uuid\":\"sc:identifier\"}],\"actor\":\"http://mobilizon.test/relay\",\"cc\":[\"https://www.w3.org/ns/activitystreams#Public\"],\"id\":\"http://mobilizon.test/follow/69/activity\",\"object\":\"http://localhost:8080/actor\",\"to\":[\"http://localhost:8080/actor\"],\"type\":\"Follow\"}",
+ "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://litepub.social/litepub/context.jsonld\",{\"Hashtag\":\"as:Hashtag\",\"category\":\"sc:category\",\"ical\":\"http://www.w3.org/2002/12/cal/ical#\",\"joinMode\":{\"@id\":\"mz:joinMode\",\"@type\":\"mz:joinModeType\"},\"joinModeType\":{\"@id\":\"mz:joinModeType\",\"@type\":\"rdfs:Class\"},\"maximumAttendeeCapacity\":\"sc:maximumAttendeeCapacity\",\"mz\":\"https://joinmobilizon.org/ns#\",\"repliesModerationOption\":{\"@id\":\"mz:repliesModerationOption\",\"@type\":\"mz:repliesModerationOptionType\"},\"repliesModerationOptionType\":{\"@id\":\"mz:repliesModerationOptionType\",\"@type\":\"rdfs:Class\"},\"sc\":\"http://schema.org#\",\"uuid\":\"sc:identifier\"}],\"actor\":\"http://mobilizon.test/relay\",\"cc\":[\"https://www.w3.org/ns/activitystreams#Public\"],\"id\":\"http://mobilizon.test/follow/b7791977-2a75-4715-815b-6e7125065b71\",\"object\":\"http://mobilizon1.com/relay\",\"to\":[\"http://mobilizon1.com/relay\"],\"type\":\"Follow\"}",
"headers": {
"Content-Type": "application/activity+json",
- "signature": "keyId=\"http://mobilizon.test/relay#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) content-length date digest host\",signature=\"UADlb5eaeqmujO5zGfK1mWB3WZFXU6lkUgSvEf5YyQMOIkMaudDwTfNPIa4IYh2VMLwyYSjOOXxkcBdCw4f9UnMBQBhomPNRNkJ0QBzoxILPmyxddAojH9IzwwAUL/nHSGWaO116bkCux0OcEM5AVIrCT6dENep39lOjnOGPelBB5mKMS78AxH4pU/5tTGFKmNgiRL4Q06ezPUJHKauRrMwzcqZYdjUn+U9MDBDrYyfAzqQlgBPU/fMCjwusndxaICb9c+40YE3WaXzKewIivfrMoOBzWyw6ZsgAG8/NoOH+8z9Z+hBvdjCUXeG2bvAPPclNkSJillwIA2PnMOVgpw==\"",
- "digest": "SHA-256=Ady0Dj2bEXe201P9bThLaj1Kw/7O1cfrjN9IifEfVBg=",
- "date": "Thu, 01 Aug 2019 14:44:38 GMT"
+ "signature": "keyId=\"http://mobilizon.test/relay#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) content-length date digest host\",signature=\"WbyGHT/WdvdRpWek8uCGHrFSblLpg+Iq802R5S2cjNj035OKpxRmu1r8u9Qr5KGIKgZn6LHt9YmB+PNlwsubPtTSkJpE8AAUDMHLKgCrH7A5Q6x6GlARl5bHNo4QtOxkXvnEbn31xfNDNp70QqZb/emw95TnELYUlMLZds0qYutT8U4WdDhSWcVytQmKJWNZXxEj+KlMDUaxag3lGscJ/HY0F+yGNov7FHthid1Y4LTGFsp/tismnMTlba12NH/kXPHtduNsX8uxFslM2ODwqAaospTGEpXmr9CPgbNy7626qgYaR2RdB/fYlCayLI4JJIlH8gOdocGHPrWNtVEHaQ==\"",
+ "digest": "SHA-256=ibNFcsnBeCCjWZo9We60tKfbRN3el0WCMVdOxtuC1cg=",
+ "date": "Fri, 13 Dec 2019 09:41:41 GMT"
},
"method": "post",
"options": {
"pool": "default"
},
"request_body": "",
- "url": "http://localhost:8080/inbox"
+ "url": "http://mobilizon1.com/inbox"
},
"response": {
"binary": false,
- "body": "signature check failed, signature did not match key",
+ "body": "# HTTPoison.Error at POST /inbox\n\nException:\n\n ** (HTTPoison.Error) :nxdomain\n (httpoison) lib/httpoison.ex:128: HTTPoison.request!/5\n (mobilizon) lib/service/activity_pub/activity_pub.ex:610: Mobilizon.Service.ActivityPub.fetch_and_prepare_actor_from_url/1\n (mobilizon) lib/service/activity_pub/activity_pub.ex:473: Mobilizon.Service.ActivityPub.make_actor_from_url/2\n (mobilizon) lib/service/activity_pub/activity_pub.ex:122: Mobilizon.Service.ActivityPub.get_or_fetch_actor_by_url/2\n (mobilizon) lib/service/http_signatures/signature.ex:54: Mobilizon.Service.HTTPSignatures.Signature.get_public_key_for_url/1\n (mobilizon) lib/service/http_signatures/signature.ex:74: Mobilizon.Service.HTTPSignatures.Signature.fetch_public_key/1\n (http_signatures) lib/http_signatures/http_signatures.ex:40: HTTPSignatures.validate_conn/1\n (mobilizon) lib/mobilizon_web/http_signature.ex:45: MobilizonWeb.HTTPSignaturePlug.call/2\n (mobilizon) MobilizonWeb.Router.activity_pub_signature/2\n (mobilizon) lib/mobilizon_web/router.ex:1: MobilizonWeb.Router.__pipe_through7__/1\n (phoenix) lib/phoenix/router.ex:283: Phoenix.Router.__call__/2\n (mobilizon) lib/mobilizon_web/endpoint.ex:1: MobilizonWeb.Endpoint.plug_builder_call/2\n (mobilizon) lib/plug/debugger.ex:122: MobilizonWeb.Endpoint.\"call (overridable 3)\"/2\n (mobilizon) lib/mobilizon_web/endpoint.ex:1: MobilizonWeb.Endpoint.call/2\n (phoenix) lib/phoenix/endpoint/cowboy2_handler.ex:42: Phoenix.Endpoint.Cowboy2Handler.init/4\n (cowboy) /home/tcit/dev/frama/mobilizon/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2\n (cowboy) /home/tcit/dev/frama/mobilizon/deps/cowboy/src/cowboy_stream_h.erl:320: :cowboy_stream_h.execute/3\n (cowboy) /home/tcit/dev/frama/mobilizon/deps/cowboy/src/cowboy_stream_h.erl:302: :cowboy_stream_h.request_process/3\n (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3\n \n\n## Connection details\n\n### Params\n\n %{\"@context\" => [\"https://www.w3.org/ns/activitystreams\", \"https://litepub.social/litepub/context.jsonld\", %{\"Hashtag\" => \"as:Hashtag\", \"category\" => \"sc:category\", \"ical\" => \"http://www.w3.org/2002/12/cal/ical#\", \"joinMode\" => %{\"@id\" => \"mz:joinMode\", \"@type\" => \"mz:joinModeType\"}, \"joinModeType\" => %{\"@id\" => \"mz:joinModeType\", \"@type\" => \"rdfs:Class\"}, \"maximumAttendeeCapacity\" => \"sc:maximumAttendeeCapacity\", \"mz\" => \"https://joinmobilizon.org/ns#\", \"repliesModerationOption\" => %{\"@id\" => \"mz:repliesModerationOption\", \"@type\" => \"mz:repliesModerationOptionType\"}, \"repliesModerationOptionType\" => %{\"@id\" => \"mz:repliesModerationOptionType\", \"@type\" => \"rdfs:Class\"}, \"sc\" => \"http://schema.org#\", \"uuid\" => \"sc:identifier\"}], \"actor\" => \"http://mobilizon.test/relay\", \"cc\" => [\"https://www.w3.org/ns/activitystreams#Public\"], \"id\" => \"http://mobilizon.test/follow/b7791977-2a75-4715-815b-6e7125065b71\", \"object\" => \"http://mobilizon1.com/relay\", \"to\" => [\"http://mobilizon1.com/relay\"], \"type\" => \"Follow\"}\n\n### Request info\n\n * URI: http://mobilizon1.com:80/inbox\n * Query string: \n\n### Headers\n \n * connection: upgrade\n * content-length: 912\n * content-type: application/activity+json\n * date: Fri, 13 Dec 2019 09:41:41 GMT\n * digest: SHA-256=ibNFcsnBeCCjWZo9We60tKfbRN3el0WCMVdOxtuC1cg=\n * host: mobilizon1.com\n * signature: keyId=\"http://mobilizon.test/relay#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) content-length date digest host\",signature=\"WbyGHT/WdvdRpWek8uCGHrFSblLpg+Iq802R5S2cjNj035OKpxRmu1r8u9Qr5KGIKgZn6LHt9YmB+PNlwsubPtTSkJpE8AAUDMHLKgCrH7A5Q6x6GlARl5bHNo4QtOxkXvnEbn31xfNDNp70QqZb/emw95TnELYUlMLZds0qYutT8U4WdDhSWcVytQmKJWNZXxEj+KlMDUaxag3lGscJ/HY0F+yGNov7FHthid1Y4LTGFsp/tismnMTlba12NH/kXPHtduNsX8uxFslM2ODwqAaospTGEpXmr9CPgbNy7626qgYaR2RdB/fYlCayLI4JJIlH8gOdocGHPrWNtVEHaQ==\"\n * user-agent: hackney/1.15.2\n * x-forwarded-for: 127.0.0.1\n * x-real-ip: 127.0.0.1\n\n### Session\n\n %{}\n",
"headers": {
- "Content-Length": "51",
- "Content-Type": "text/plain; charset=utf-8",
- "Date": "Thu, 01 Aug 2019 14:44:38 GMT",
- "Server": "Python/3.7 aiohttp/3.3.2"
+ "Server": "nginx/1.16.1",
+ "Date": "Fri, 13 Dec 2019 09:41:41 GMT",
+ "Content-Type": "text/markdown; charset=utf-8",
+ "Content-Length": "3977",
+ "Connection": "keep-alive",
+ "access-control-allow-credentials": "true",
+ "access-control-allow-origin": "*",
+ "access-control-expose-headers": "",
+ "cache-control": "max-age=0, private, must-revalidate",
+ "x-request-id": "Fd_k7PoZpCCBYRQAAAPh"
},
- "status_code": 401,
+ "status_code": 500,
"type": "ok"
}
}
diff --git a/test/fixtures/vcr_cassettes/relay/fetch_relay_unfollow.json b/test/fixtures/vcr_cassettes/relay/fetch_relay_unfollow.json
index 0c2abfe16..7b743e63f 100644
--- a/test/fixtures/vcr_cassettes/relay/fetch_relay_unfollow.json
+++ b/test/fixtures/vcr_cassettes/relay/fetch_relay_unfollow.json
@@ -1,30 +1,33 @@
[
{
"request": {
- "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://litepub.github.io/litepub/context.jsonld\",{\"Hashtag\":\"as:Hashtag\",\"category\":\"sc:category\",\"sc\":\"http://schema.org#\",\"uuid\":\"sc:identifier\"}],\"actor\":\"http://mobilizon.test/relay\",\"cc\":[\"https://www.w3.org/ns/activitystreams#Public\"],\"id\":\"http://mobilizon.test/follow/68/activity\",\"object\":\"http://localhost:8080/actor\",\"to\":[\"http://localhost:8080/actor\"],\"type\":\"Follow\"}",
+ "body": "",
"headers": {
- "Content-Type": "application/activity+json",
- "signature": "keyId=\"http://mobilizon.test/relay#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) content-length date digest host\",signature=\"WsxzipdObXsApVtY5l2yTonTOPV888XLKK2+AMQRyiNZm4RGMEux8kgBKgJIODaKmRx9EsX8dIzBtTmJdLyj5gqfjvGVyj8hVeR0ERNMZmjngh5EZ3W+ySbkdFYZeYDWhwpL1i+7dTFJ3zE/ASZVaTMeIgqEpFnzHNbamwPzBZVvcnzyraB1rrmwcbzzrk3UPlJ3tA+Xz67Njr2wOiNNsjZ53abArKZB3KGbife6OyrVrKldJ+UKZS+vokgUXFwvMBZxfdmH2GD+yXHPhCIu7bVu77ASdW7bl7tM3uIV/c/Wemy5qJtPOupwbDvpLZ9ETE5IRCoUPdQ7l75kvevNxQ==\"",
- "digest": "SHA-256=qIEgTH6kBorFchTiX2kxd7onyZ7BHhvLgCODLs6RAVc=",
- "date": "Thu, 01 Aug 2019 14:44:37 GMT"
+ "Accept": "application/json, application/activity+json, application/jrd+json"
},
- "method": "post",
+ "method": "get",
"options": {
- "pool": "default"
+ "follow_redirect": "true"
},
"request_body": "",
- "url": "http://localhost:8080/inbox"
+ "url": "http://mobilizon1.com/.well-known/webfinger?resource=acct:relay@mobilizon1.com"
},
"response": {
"binary": false,
- "body": "signature check failed, signature did not match key",
+ "body": "{\"aliases\":[\"http://mobilizon1.com/relay\"],\"links\":[{\"href\":\"http://mobilizon1.com/relay\",\"rel\":\"self\",\"type\":\"application/activity+json\"},{\"href\":\"http://mobilizon1.com/relay\",\"rel\":\"https://webfinger.net/rel/profile-page/\",\"type\":\"text/html\"}],\"subject\":\"acct:relay@mobilizon1.com\"}",
"headers": {
- "Content-Length": "51",
- "Content-Type": "text/plain; charset=utf-8",
- "Date": "Thu, 01 Aug 2019 14:44:37 GMT",
- "Server": "Python/3.7 aiohttp/3.3.2"
+ "Server": "nginx/1.16.1",
+ "Date": "Fri, 13 Dec 2019 09:41:39 GMT",
+ "Content-Type": "application/json; charset=utf-8",
+ "Content-Length": "284",
+ "Connection": "keep-alive",
+ "access-control-allow-credentials": "true",
+ "access-control-allow-origin": "*",
+ "access-control-expose-headers": "",
+ "cache-control": "max-age=0, private, must-revalidate",
+ "x-request-id": "Fd_k7LmY5k0CMQkAAANB"
},
- "status_code": 401,
+ "status_code": 200,
"type": "ok"
}
},
@@ -39,19 +42,60 @@
"follow_redirect": "true"
},
"request_body": "",
- "url": "http://localhost:8080/actor"
+ "url": "http://mobilizon1.com/relay"
},
"response": {
"binary": false,
- "body": "{\"@context\": \"https://www.w3.org/ns/activitystreams\", \"endpoints\": {\"sharedInbox\": \"http://localhost:8080/inbox\"}, \"followers\": \"http://localhost:8080/followers\", \"following\": \"http://localhost:8080/following\", \"inbox\": \"http://localhost:8080/inbox\", \"name\": \"ActivityRelay\", \"type\": \"Application\", \"id\": \"http://localhost:8080/actor\", \"publicKey\": {\"id\": \"http://localhost:8080/actor#main-key\", \"owner\": \"http://localhost:8080/actor\", \"publicKeyPem\": \"-----BEGIN PUBLIC KEY-----\\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvs6UuAo26Sb3BiOK7xay\\nsBzqvXI3xd55JAP0pAk2faF+Vl3r67/g9MoND96JqCVMuzSJZ9oSsqa6ilJCxG3p\\nXUUfQUvqAMGW49cCvga86DG17Ennjbc4C6WIQtoW3Wm5OdDciPY2Dx+pSXdTOajB\\nFX6RHUZcgqHENrsm3jPZI138e/2OJeqdxv4/5t2xdPXEpWdPGitX9AJhrqPY4lzg\\nzQ9Y9wS2eS1CVL9vZZRf9Z4RiZvAfVb0s1iS/IUxrf4TYERRFJxEoDLD2SZVrkq6\\nvhGldCfw2ZnfTftA1ToXguC9S6nSaz+li0ajNjpK/xjZjlKvn0I078UPPe5LUlsb\\nUcYZvBx5PC5rV8yKMLlgxnTY8PqC8LEVc453wO7Ai4M5TeB0SUyEycZHSyLfvQXV\\nThEN/07u1UaJViY3U5S/SihyoCQUfJXQ3jx2SjGgM32/aJ3IwxgveLaTsaZ0VVKM\\nbawEFw6iAcWYM06hZSB6j6dkL1xh+FYGEQTPMYMqUOJi2r1cD8yMLe8dTFOmwMLt\\nBnf7xxvnjKJcv3e9zGRWIdLkQbBQn3BEuRTCUMgljipxdjbeE5/JSP1kQLB94ncb\\nb9gvYgtemJKvT8m37+HOi9MI4BMIlDwpRWjqPZmkNvkegR/1KPjJSsyAnGdd89ne\\np442vUqPyXIq0tSCDmjmU+cCAwEAAQ==\\n-----END PUBLIC KEY-----\"}, \"summary\": \"ActivityRelay bot\", \"preferredUsername\": \"relay\", \"url\": \"http://localhost:8080/actor\"}",
+ "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://litepub.social/litepub/context.jsonld\",{\"Hashtag\":\"as:Hashtag\",\"category\":\"sc:category\",\"ical\":\"http://www.w3.org/2002/12/cal/ical#\",\"joinMode\":{\"@id\":\"mz:joinMode\",\"@type\":\"mz:joinModeType\"},\"joinModeType\":{\"@id\":\"mz:joinModeType\",\"@type\":\"rdfs:Class\"},\"maximumAttendeeCapacity\":\"sc:maximumAttendeeCapacity\",\"mz\":\"https://joinmobilizon.org/ns#\",\"repliesModerationOption\":{\"@id\":\"mz:repliesModerationOption\",\"@type\":\"mz:repliesModerationOptionType\"},\"repliesModerationOptionType\":{\"@id\":\"mz:repliesModerationOptionType\",\"@type\":\"rdfs:Class\"},\"sc\":\"http://schema.org#\",\"uuid\":\"sc:identifier\"}],\"endpoints\":{\"sharedInbox\":\"http://mobilizon1.com/inbox\"},\"followers\":\"http://mobilizon1.com/relay/followers\",\"following\":\"http://mobilizon1.com/relay/following\",\"id\":\"http://mobilizon1.com/relay\",\"inbox\":\"http://mobilizon1.com/inbox\",\"manuallyApprovesFollowers\":false,\"name\":\"Mobilizon\",\"outbox\":null,\"preferredUsername\":\"relay\",\"publicKey\":{\"id\":\"http://mobilizon1.com/relay#main-key\",\"owner\":\"http://mobilizon1.com/relay\",\"publicKeyPem\":\"-----BEGIN RSA PUBLIC KEY-----\\nMIIBCgKCAQEAqBbeHMV5UVw0AIVch7fWDp2it5rqbGZX6yXPYnnT8LHhdvfv3DFk\\npk74BN66MzNqsthvSVznu2BEil0sEKD5rQoE9Yirhzz/LN9SlnU+u6262nBA18E3\\nkQ10RgL2jpZ9e8Om6qYqarhN7draupJXYRKEaUoEFPT09ABbwQv+4K1YadU8klJi\\nHJ6D+IIHiXNizfsxVLDKpbUKStMYeEzyfqCkWw0EQEuzc3O7Aci5lwCMkCts2993\\nsTbNyzsYAVWJNcy/An1F1P+K4iZhWEtZInQz67MBtjMWtQUhyWib0e671HdBiWM6\\nkZq74U8c6RR6eMzBLuY7YAUCG6nWg90zxwIDAQAB\\n-----END RSA PUBLIC KEY-----\\n\\n\"},\"summary\":\"Change this to a proper description of your instance\",\"type\":\"Application\",\"url\":\"http://mobilizon1.com/relay\"}",
"headers": {
- "Content-Type": "application/json; charset=utf-8",
- "Content-Length": "1368",
- "Date": "Thu, 01 Aug 2019 14:44:36 GMT",
- "Server": "Python/3.7 aiohttp/3.3.2"
+ "Server": "nginx/1.16.1",
+ "Date": "Fri, 13 Dec 2019 09:41:40 GMT",
+ "Content-Type": "application/activity+json",
+ "Content-Length": "1657",
+ "Connection": "keep-alive",
+ "access-control-allow-credentials": "true",
+ "access-control-allow-origin": "*",
+ "access-control-expose-headers": "",
+ "cache-control": "max-age=0, private, must-revalidate",
+ "x-request-id": "Fd_k7L4h92fDp5cAAANh"
},
"status_code": 200,
"type": "ok"
}
+ },
+ {
+ "request": {
+ "body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://litepub.social/litepub/context.jsonld\",{\"Hashtag\":\"as:Hashtag\",\"category\":\"sc:category\",\"ical\":\"http://www.w3.org/2002/12/cal/ical#\",\"joinMode\":{\"@id\":\"mz:joinMode\",\"@type\":\"mz:joinModeType\"},\"joinModeType\":{\"@id\":\"mz:joinModeType\",\"@type\":\"rdfs:Class\"},\"maximumAttendeeCapacity\":\"sc:maximumAttendeeCapacity\",\"mz\":\"https://joinmobilizon.org/ns#\",\"repliesModerationOption\":{\"@id\":\"mz:repliesModerationOption\",\"@type\":\"mz:repliesModerationOptionType\"},\"repliesModerationOptionType\":{\"@id\":\"mz:repliesModerationOptionType\",\"@type\":\"rdfs:Class\"},\"sc\":\"http://schema.org#\",\"uuid\":\"sc:identifier\"}],\"actor\":\"http://mobilizon.test/relay\",\"cc\":[\"https://www.w3.org/ns/activitystreams#Public\"],\"id\":\"http://mobilizon.test/follow/57a6973e-f43f-4533-bf71-7a14a4c6e5ac\",\"object\":\"http://mobilizon1.com/relay\",\"to\":[\"http://mobilizon1.com/relay\"],\"type\":\"Follow\"}",
+ "headers": {
+ "Content-Type": "application/activity+json",
+ "signature": "keyId=\"http://mobilizon.test/relay#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) content-length date digest host\",signature=\"JQPqSiJ0ZYdU6llrYXNMuN/bfzoLyubwOB59bljFq6i8ORXLw62Pt7Jue5WkMsySFcCXgS8k8K/H81YZkKzfWadwQV9L5rQEFSuW/DYJ2xffsDj90GsSi+sDRaQ5Ke8nPEbEMGR9jalh/F2VL97XscCgm6i3tdpbs6aFmqjKC+LzeH665t0WCHUxTgK47wECrMHw3j7lteGdm6N6IKWoWsRYeJoyFr/QCbNdWQOaAYYpCbJd0fjhPQRHhWQXidBoaDkhwesWc3mO8pvEnply9ES7Nzc6ULK7B98hg+aWeep8/KzRbxFyJ0OgnDJj/l39QiJ9t7v0yHX/WUzn0CaiiQ==\"",
+ "digest": "SHA-256=Qc9d9X3qh2EqIqtn/72iY17OMDXAOINDC10hARNAc4w=",
+ "date": "Fri, 13 Dec 2019 09:41:40 GMT"
+ },
+ "method": "post",
+ "options": {
+ "pool": "default"
+ },
+ "request_body": "",
+ "url": "http://mobilizon1.com/inbox"
+ },
+ "response": {
+ "binary": false,
+ "body": "# HTTPoison.Error at POST /inbox\n\nException:\n\n ** (HTTPoison.Error) :nxdomain\n (httpoison) lib/httpoison.ex:128: HTTPoison.request!/5\n (mobilizon) lib/service/activity_pub/activity_pub.ex:610: Mobilizon.Service.ActivityPub.fetch_and_prepare_actor_from_url/1\n (mobilizon) lib/service/activity_pub/activity_pub.ex:473: Mobilizon.Service.ActivityPub.make_actor_from_url/2\n (mobilizon) lib/service/activity_pub/activity_pub.ex:122: Mobilizon.Service.ActivityPub.get_or_fetch_actor_by_url/2\n (mobilizon) lib/service/http_signatures/signature.ex:54: Mobilizon.Service.HTTPSignatures.Signature.get_public_key_for_url/1\n (mobilizon) lib/service/http_signatures/signature.ex:74: Mobilizon.Service.HTTPSignatures.Signature.fetch_public_key/1\n (http_signatures) lib/http_signatures/http_signatures.ex:40: HTTPSignatures.validate_conn/1\n (mobilizon) lib/mobilizon_web/http_signature.ex:45: MobilizonWeb.HTTPSignaturePlug.call/2\n (mobilizon) MobilizonWeb.Router.activity_pub_signature/2\n (mobilizon) lib/mobilizon_web/router.ex:1: MobilizonWeb.Router.__pipe_through7__/1\n (phoenix) lib/phoenix/router.ex:283: Phoenix.Router.__call__/2\n (mobilizon) lib/mobilizon_web/endpoint.ex:1: MobilizonWeb.Endpoint.plug_builder_call/2\n (mobilizon) lib/plug/debugger.ex:122: MobilizonWeb.Endpoint.\"call (overridable 3)\"/2\n (mobilizon) lib/mobilizon_web/endpoint.ex:1: MobilizonWeb.Endpoint.call/2\n (phoenix) lib/phoenix/endpoint/cowboy2_handler.ex:42: Phoenix.Endpoint.Cowboy2Handler.init/4\n (cowboy) /home/tcit/dev/frama/mobilizon/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2\n (cowboy) /home/tcit/dev/frama/mobilizon/deps/cowboy/src/cowboy_stream_h.erl:320: :cowboy_stream_h.execute/3\n (cowboy) /home/tcit/dev/frama/mobilizon/deps/cowboy/src/cowboy_stream_h.erl:302: :cowboy_stream_h.request_process/3\n (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3\n \n\n## Connection details\n\n### Params\n\n %{\"@context\" => [\"https://www.w3.org/ns/activitystreams\", \"https://litepub.social/litepub/context.jsonld\", %{\"Hashtag\" => \"as:Hashtag\", \"category\" => \"sc:category\", \"ical\" => \"http://www.w3.org/2002/12/cal/ical#\", \"joinMode\" => %{\"@id\" => \"mz:joinMode\", \"@type\" => \"mz:joinModeType\"}, \"joinModeType\" => %{\"@id\" => \"mz:joinModeType\", \"@type\" => \"rdfs:Class\"}, \"maximumAttendeeCapacity\" => \"sc:maximumAttendeeCapacity\", \"mz\" => \"https://joinmobilizon.org/ns#\", \"repliesModerationOption\" => %{\"@id\" => \"mz:repliesModerationOption\", \"@type\" => \"mz:repliesModerationOptionType\"}, \"repliesModerationOptionType\" => %{\"@id\" => \"mz:repliesModerationOptionType\", \"@type\" => \"rdfs:Class\"}, \"sc\" => \"http://schema.org#\", \"uuid\" => \"sc:identifier\"}], \"actor\" => \"http://mobilizon.test/relay\", \"cc\" => [\"https://www.w3.org/ns/activitystreams#Public\"], \"id\" => \"http://mobilizon.test/follow/57a6973e-f43f-4533-bf71-7a14a4c6e5ac\", \"object\" => \"http://mobilizon1.com/relay\", \"to\" => [\"http://mobilizon1.com/relay\"], \"type\" => \"Follow\"}\n\n### Request info\n\n * URI: http://mobilizon1.com:80/inbox\n * Query string: \n\n### Headers\n \n * connection: upgrade\n * content-length: 912\n * content-type: application/activity+json\n * date: Fri, 13 Dec 2019 09:41:40 GMT\n * digest: SHA-256=Qc9d9X3qh2EqIqtn/72iY17OMDXAOINDC10hARNAc4w=\n * host: mobilizon1.com\n * signature: keyId=\"http://mobilizon.test/relay#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) content-length date digest host\",signature=\"JQPqSiJ0ZYdU6llrYXNMuN/bfzoLyubwOB59bljFq6i8ORXLw62Pt7Jue5WkMsySFcCXgS8k8K/H81YZkKzfWadwQV9L5rQEFSuW/DYJ2xffsDj90GsSi+sDRaQ5Ke8nPEbEMGR9jalh/F2VL97XscCgm6i3tdpbs6aFmqjKC+LzeH665t0WCHUxTgK47wECrMHw3j7lteGdm6N6IKWoWsRYeJoyFr/QCbNdWQOaAYYpCbJd0fjhPQRHhWQXidBoaDkhwesWc3mO8pvEnply9ES7Nzc6ULK7B98hg+aWeep8/KzRbxFyJ0OgnDJj/l39QiJ9t7v0yHX/WUzn0CaiiQ==\"\n * user-agent: hackney/1.15.2\n * x-forwarded-for: 127.0.0.1\n * x-real-ip: 127.0.0.1\n\n### Session\n\n %{}\n",
+ "headers": {
+ "Server": "nginx/1.16.1",
+ "Date": "Fri, 13 Dec 2019 09:41:40 GMT",
+ "Content-Type": "text/markdown; charset=utf-8",
+ "Content-Length": "3977",
+ "Connection": "keep-alive",
+ "access-control-allow-credentials": "true",
+ "access-control-allow-origin": "*",
+ "access-control-expose-headers": "",
+ "cache-control": "max-age=0, private, must-revalidate",
+ "x-request-id": "Fd_k7MU4jVIgj4wAAAOB"
+ },
+ "status_code": 500,
+ "type": "ok"
+ }
}
]
\ No newline at end of file
diff --git a/test/mobilizon/actors/actors_test.exs b/test/mobilizon/actors/actors_test.exs
index 1d065ae43..d829ecfd5 100644
--- a/test/mobilizon/actors/actors_test.exs
+++ b/test/mobilizon/actors/actors_test.exs
@@ -1,12 +1,13 @@
defmodule Mobilizon.ActorsTest do
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
-
use Mobilizon.DataCase
+ use Oban.Testing, repo: Mobilizon.Storage.Repo
import Mobilizon.Factory
- alias Mobilizon.{Actors, Config, Users}
+ alias Mobilizon.{Actors, Config, Users, Events, Tombstone}
alias Mobilizon.Actors.{Actor, Bot, Follower, Member}
+ alias Mobilizon.Events.{Event, Comment}
alias Mobilizon.Media.File, as: FileModel
alias Mobilizon.Service.ActivityPub
alias Mobilizon.Storage.Page
@@ -287,6 +288,12 @@ defmodule Mobilizon.ActorsTest do
test "delete_actor/1 deletes the actor", %{
actor: %Actor{avatar: %{url: avatar_url}, banner: %{url: banner_url}, id: actor_id} = actor
} do
+ %Event{url: event1_url} = event1 = insert(:event, organizer_actor: actor)
+ insert(:event, organizer_actor: actor)
+
+ %Comment{url: comment1_url} = comment1 = insert(:comment, actor: actor)
+ insert(:comment, actor: actor)
+
%URI{path: "/media/" <> avatar_path} = URI.parse(avatar_url)
%URI{path: "/media/" <> banner_path} = URI.parse(banner_url)
@@ -300,8 +307,34 @@ defmodule Mobilizon.ActorsTest do
"/" <> banner_path
)
- assert {:ok, %Actor{}} = Actors.delete_actor(actor)
- assert_raise Ecto.NoResultsError, fn -> Actors.get_actor!(actor_id) end
+ assert {:ok, %Oban.Job{}} = Actors.delete_actor(actor)
+
+ assert_enqueued(
+ worker: Mobilizon.Service.Workers.BackgroundWorker,
+ args: %{"actor_id" => actor.id, "op" => "delete_actor"}
+ )
+
+ assert %{success: 1, failure: 0} == Oban.drain_queue(:background)
+
+ assert %Actor{
+ name: nil,
+ summary: nil,
+ suspended: true,
+ avatar: nil,
+ banner: nil,
+ user_id: nil
+ } = Actors.get_actor(actor_id)
+
+ assert {:error, :event_not_found} = Events.get_event(event1.id)
+ assert %Tombstone{} = Tombstone.find_tombstone(event1_url)
+ assert %Comment{deleted_at: deleted_at} = Events.get_comment(comment1.id)
+ refute is_nil(deleted_at)
+ assert %Tombstone{} = Tombstone.find_tombstone(comment1_url)
+
+ refute File.exists?(
+ Config.get!([MobilizonWeb.Uploaders.Local, :uploads]) <>
+ "/" <> avatar_path
+ )
refute File.exists?(
Config.get!([MobilizonWeb.Uploaders.Local, :uploads]) <>
diff --git a/test/mobilizon/events/events_test.exs b/test/mobilizon/events/events_test.exs
index bc0e4ba7a..5ecac1ae0 100644
--- a/test/mobilizon/events/events_test.exs
+++ b/test/mobilizon/events/events_test.exs
@@ -314,7 +314,10 @@ defmodule Mobilizon.EventsTest do
setup do
actor = insert(:actor)
- event = insert(:event, organizer_actor: actor)
+
+ event =
+ insert(:event, organizer_actor: actor, participant_stats: %{creator: 1, participant: 1})
+
participant = insert(:participant, actor: actor, event: event)
{:ok, participant: participant, event: event, actor: actor}
end
@@ -364,7 +367,8 @@ defmodule Mobilizon.EventsTest do
test "update_participant/2 with invalid data returns error changeset", %{
participant: participant
} do
- assert {:error, %Ecto.Changeset{}} = Events.update_participant(participant, @invalid_attrs)
+ assert {:error, :participant, %Ecto.Changeset{}, %{}} =
+ Events.update_participant(participant, @invalid_attrs)
end
test "delete_participant/1 deletes the participant", %{participant: participant} do
diff --git a/test/mobilizon/service/activity_pub/activity_pub_test.exs b/test/mobilizon/service/activity_pub/activity_pub_test.exs
index 63f781677..db7683885 100644
--- a/test/mobilizon/service/activity_pub/activity_pub_test.exs
+++ b/test/mobilizon/service/activity_pub/activity_pub_test.exs
@@ -171,7 +171,7 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do
assert update.data["actor"] == actor.url
assert update.data["to"] == [@activity_pub_public_audience]
assert update.data["object"]["id"] == actor.url
- assert update.data["object"]["type"] == "Person"
+ assert update.data["object"]["type"] == :Person
assert update.data["object"]["summary"] == @updated_actor_summary
end
diff --git a/test/mobilizon/service/activity_pub/converter/actor_test.exs b/test/mobilizon/service/activity_pub/converter/actor_test.exs
index 88485ed70..edc8b0ee9 100644
--- a/test/mobilizon/service/activity_pub/converter/actor_test.exs
+++ b/test/mobilizon/service/activity_pub/converter/actor_test.exs
@@ -8,8 +8,8 @@ defmodule Mobilizon.Service.ActivityPub.Converter.ActorTest do
test "valid actor to as" do
data = ActorConverter.model_to_as(%Actor{type: :Person, preferred_username: "test_account"})
assert is_map(data)
- assert data["type"] == "Person"
- assert data["preferred_username"] == "test_account"
+ assert data["type"] == :Person
+ assert data["preferredUsername"] == "test_account"
end
end
@@ -17,12 +17,13 @@ defmodule Mobilizon.Service.ActivityPub.Converter.ActorTest do
test "valid as data to model" do
{:ok, actor} =
ActorConverter.as_to_model_data(%{
+ "id" => "https://somedomain.tld/users/someone",
"type" => "Person",
"preferredUsername" => "test_account"
})
- assert actor["type"] == :Person
- assert actor["preferred_username"] == "test_account"
+ assert actor.type == "Person"
+ assert actor.preferred_username == "test_account"
end
end
end
diff --git a/test/mobilizon/service/activity_pub/transmogrifier_test.exs b/test/mobilizon/service/activity_pub/transmogrifier_test.exs
index 995881ef4..afc191739 100644
--- a/test/mobilizon/service/activity_pub/transmogrifier_test.exs
+++ b/test/mobilizon/service/activity_pub/transmogrifier_test.exs
@@ -9,10 +9,10 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
use Mobilizon.DataCase
import Mobilizon.Factory
+ import ExUnit.CaptureLog
- alias Mobilizon.Actors
+ alias Mobilizon.{Actors, Events, Tombstone}
alias Mobilizon.Actors.Actor
- alias Mobilizon.Events
alias Mobilizon.Events.{Comment, Event, Participant}
alias Mobilizon.Service.ActivityPub
alias Mobilizon.Service.ActivityPub.{Activity, Utils}
@@ -131,7 +131,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
data
|> Map.put("object", object)
- assert ExUnit.CaptureLog.capture_log([level: :warn], fn ->
+ assert capture_log([level: :warn], fn ->
{:ok, _returned_activity, _entity} = Transmogrifier.handle_incoming(data)
end) =~ "[warn] Parent object is something we don't handle"
end
@@ -145,7 +145,10 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
assert data["id"] ==
"https://framapiaf.org/users/admin/statuses/99512778738411822/activity"
- assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
+ assert data["to"] == [
+ "https://www.w3.org/ns/activitystreams#Public",
+ "https://framapiaf.org/users/tcit"
+ ]
# assert data["cc"] == [
# "https://framapiaf.org/users/admin/followers",
@@ -466,26 +469,70 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
refute is_nil(Events.get_comment_from_url(comment_url).deleted_at)
end
- # TODO : make me ASAP
- # test "it fails for incoming deletes with spoofed origin" do
- # activity = insert(:note_activity)
+ test "it fails for incoming deletes with spoofed origin" do
+ comment = insert(:comment)
- # data =
- # File.read!("test/fixtures/mastodon-delete.json")
- # |> Jason.decode!()
+ announce_data =
+ File.read!("test/fixtures/mastodon-announce.json")
+ |> Jason.decode!()
+ |> Map.put("object", comment.url)
- # object =
- # data["object"]
- # |> Map.put("id", activity.data["object"]["id"])
+ {:ok, %Activity{local: false}, _} = Transmogrifier.handle_incoming(announce_data)
- # data =
- # data
- # |> Map.put("object", object)
+ data =
+ File.read!("test/fixtures/mastodon-delete.json")
+ |> Jason.decode!()
- # :error = Transmogrifier.handle_incoming(data)
+ object =
+ data["object"]
+ |> Map.put("id", comment.url)
- # assert Repo.get(Activity, activity.id)
- # end
+ data =
+ data
+ |> Map.put("object", object)
+
+ :error = Transmogrifier.handle_incoming(data)
+
+ assert Events.get_comment_from_url(comment.url)
+ end
+
+ test "it works for incoming actor deletes" do
+ %Actor{url: url} = actor = insert(:actor, url: "https://framapiaf.org/users/admin")
+ %Event{url: event1_url} = event1 = insert(:event, organizer_actor: actor)
+ insert(:event, organizer_actor: actor)
+
+ %Comment{url: comment1_url} = comment1 = insert(:comment, actor: actor)
+ insert(:comment, actor: actor)
+
+ data =
+ File.read!("test/fixtures/mastodon-delete-user.json")
+ |> Poison.decode!()
+
+ {:ok, _activity, _actor} = Transmogrifier.handle_incoming(data)
+ assert %{success: 1, failure: 0} == Oban.drain_queue(:background)
+
+ assert {:ok, %Actor{suspended: true}} = Actors.get_actor_by_url(url)
+ assert {:error, :event_not_found} = Events.get_event(event1.id)
+ assert %Tombstone{} = Tombstone.find_tombstone(event1_url)
+ assert %Comment{deleted_at: deleted_at} = Events.get_comment(comment1.id)
+ refute is_nil(deleted_at)
+ assert %Tombstone{} = Tombstone.find_tombstone(comment1_url)
+ end
+
+ test "it fails for incoming actor deletes with spoofed origin" do
+ %{url: url} = insert(:actor)
+
+ data =
+ File.read!("test/fixtures/mastodon-delete-user.json")
+ |> Poison.decode!()
+ |> Map.put("actor", url)
+
+ assert capture_log(fn ->
+ assert :error == Transmogrifier.handle_incoming(data)
+ end) =~ "Object origin check failed"
+
+ assert Actors.get_actor_by_url(url)
+ end
test "it works for incoming unannounces with an existing notice" do
use_cassette "activity_pub/mastodon_unannounce_activity" do
@@ -743,13 +790,14 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
end
test "it accepts Flag activities" do
- %Actor{url: reporter_url} = _reporter = insert(:actor)
+ %Actor{url: reporter_url} = Mobilizon.Service.ActivityPub.Relay.get_actor()
%Actor{url: reported_url} = reported = insert(:actor)
%Comment{url: comment_url} = _comment = insert(:comment, actor: reported)
message = %{
"@context" => "https://www.w3.org/ns/activitystreams",
+ "to" => [],
"cc" => [reported_url],
"object" => [reported_url, comment_url],
"type" => "Flag",
@@ -762,11 +810,11 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
assert activity.data["object"] == [reported_url, comment_url]
assert activity.data["content"] == "blocked AND reported!!!"
assert activity.data["actor"] == reporter_url
- assert activity.data["cc"] == [reported_url]
+ assert activity.data["cc"] == []
end
test "it accepts Join activities" do
- %Actor{url: _organizer_url} = organizer = insert(:actor)
+ %Actor{url: organizer_url} = organizer = insert(:actor)
%Actor{url: participant_url} = _participant = insert(:actor)
%Event{url: event_url} = _event = insert(:event, organizer_actor: organizer)
@@ -779,8 +827,12 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
assert {:ok, activity, _} = Transmogrifier.handle_incoming(join_data)
- assert activity.data["object"] == event_url
- assert activity.data["actor"] == participant_url
+ assert activity.data["type"] == "Accept"
+ assert activity.data["object"]["object"] == event_url
+ assert activity.data["object"]["id"] =~ "/join/event/"
+ assert activity.data["object"]["type"] =~ "Join"
+ assert activity.data["actor"] == organizer_url
+ assert activity.data["id"] =~ "/accept/join/"
end
test "it accepts Accept activities for Join activities" do
@@ -821,12 +873,17 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|> Map.put("object", participation.url)
{:ok, reject_activity, _} = Transmogrifier.handle_incoming(reject_data)
- assert reject_activity.data["object"] == join_activity.data["id"]
- assert reject_activity.data["object"] =~ "/join/"
+ assert reject_activity.data["object"]["id"] == join_activity.data["id"]
+ assert reject_activity.data["object"]["id"] =~ "/join/"
assert reject_activity.data["id"] =~ "/reject/join/"
# We don't accept already rejected Reject activities
- assert :error == Transmogrifier.handle_incoming(reject_data)
+ assert capture_log([level: :warn], fn ->
+ assert :error == Transmogrifier.handle_incoming(reject_data)
+ end) =~
+ "Unable to process Reject activity \"http://mastodon.example.org/users/admin#rejects/follows/4\". Object \"#{
+ join_activity.data["id"]
+ }\" wasn't found."
# Organiser is not present since we use factories directly
assert event.id
@@ -913,15 +970,6 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
assert Enum.member?(object["tag"], expected_mention)
end
- # test "it adds the sensitive property" do
- # user = insert(:user)
-
- # {:ok, activity} = CommonAPI.post(user, %{"status" => "#nsfw hey"})
- # {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
-
- # assert modified["object"]["sensitive"]
- # end
-
test "it adds the json-ld context and the conversation property" do
actor = insert(:actor)
@@ -975,125 +1023,28 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
assert is_nil(modified["object"]["announcement_count"])
assert is_nil(modified["object"]["context_id"])
end
+ end
- # describe "actor rewriting" do
- # test "it fixes the actor URL property to be a proper URI" do
- # data = %{
- # "url" => %{"href" => "http://example.com"}
- # }
+ describe "actor origin check" do
+ test "it rejects objects with a bogus origin" do
+ use_cassette "activity_pub/object_bogus_origin" do
+ {:error, _} = ActivityPub.fetch_object_from_url("https://info.pleroma.site/activity.json")
+ end
+ end
- # rewritten = Transmogrifier.maybe_fix_user_object(data)
- # assert rewritten["url"] == "http://example.com"
- # end
- # end
+ test "it rejects activities which reference objects with bogus origins" do
+ use_cassette "activity_pub/activity_object_bogus" do
+ data = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "id" => "https://framapiaf.org/users/admin/activities/1234",
+ "actor" => "https://framapiaf.org/users/admin",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "object" => "https://info.pleroma.site/activity.json",
+ "type" => "Announce"
+ }
- # describe "actor origin containment" do
- # test "it rejects objects with a bogus origin" do
- # {:error, _} = ActivityPub.fetch_object_from_id("https://info.pleroma.site/activity.json")
- # end
-
- # test "it rejects activities which reference objects with bogus origins" do
- # data = %{
- # "@context" => "https://www.w3.org/ns/activitystreams",
- # "id" => "http://mastodon.example.org/users/admin/activities/1234",
- # "actor" => "http://mastodon.example.org/users/admin",
- # "to" => ["https://www.w3.org/ns/activitystreams#Public"],
- # "object" => "https://info.pleroma.site/activity.json",
- # "type" => "Announce"
- # }
-
- # :error = Transmogrifier.handle_incoming(data)
- # end
-
- # test "it rejects objects when attributedTo is wrong (variant 1)" do
- # {:error, _} = ActivityPub.fetch_object_from_id("https://info.pleroma.site/activity2.json")
- # end
-
- # test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
- # data = %{
- # "@context" => "https://www.w3.org/ns/activitystreams",
- # "id" => "http://mastodon.example.org/users/admin/activities/1234",
- # "actor" => "http://mastodon.example.org/users/admin",
- # "to" => ["https://www.w3.org/ns/activitystreams#Public"],
- # "object" => "https://info.pleroma.site/activity2.json",
- # "type" => "Announce"
- # }
-
- # :error = Transmogrifier.handle_incoming(data)
- # end
-
- # test "it rejects objects when attributedTo is wrong (variant 2)" do
- # {:error, _} = ActivityPub.fetch_object_from_id("https://info.pleroma.site/activity3.json")
- # end
-
- # test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
- # data = %{
- # "@context" => "https://www.w3.org/ns/activitystreams",
- # "id" => "http://mastodon.example.org/users/admin/activities/1234",
- # "actor" => "http://mastodon.example.org/users/admin",
- # "to" => ["https://www.w3.org/ns/activitystreams#Public"],
- # "object" => "https://info.pleroma.site/activity3.json",
- # "type" => "Announce"
- # }
-
- # :error = Transmogrifier.handle_incoming(data)
- # end
- # end
-
- # describe "general origin containment" do
- # test "contain_origin_from_id() catches obvious spoofing attempts" do
- # data = %{
- # "id" => "http://example.com/~alyssa/activities/1234.json"
- # }
-
- # :error =
- # Transmogrifier.contain_origin_from_id(
- # "http://example.org/~alyssa/activities/1234.json",
- # data
- # )
- # end
-
- # test "contain_origin_from_id() allows alternate IDs within the same origin domain" do
- # data = %{
- # "id" => "http://example.com/~alyssa/activities/1234.json"
- # }
-
- # :ok =
- # Transmogrifier.contain_origin_from_id(
- # "http://example.com/~alyssa/activities/1234",
- # data
- # )
- # end
-
- # test "contain_origin_from_id() allows matching IDs" do
- # data = %{
- # "id" => "http://example.com/~alyssa/activities/1234.json"
- # }
-
- # :ok =
- # Transmogrifier.contain_origin_from_id(
- # "http://example.com/~alyssa/activities/1234.json",
- # data
- # )
- # end
-
- # test "users cannot be collided through fake direction spoofing attempts" do
- # user =
- # insert(:user, %{
- # nickname: "rye@niu.moe",
- # local: false,
- # ap_id: "https://niu.moe/users/rye",
- # follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
- # })
-
- # {:error, _} = User.get_or_fetch_by_ap_id("https://n1u.moe/users/rye")
- # end
-
- # test "all objects with fake directions are rejected by the object fetcher" do
- # {:error, _} =
- # ActivityPub.fetch_and_contain_remote_object_from_id(
- # "https://info.pleroma.site/activity4.json"
- # )
- # end
+ :error = Transmogrifier.handle_incoming(data)
+ end
+ end
end
end
diff --git a/test/mobilizon/service/activity_pub/utils_test.exs b/test/mobilizon/service/activity_pub/utils_test.exs
index 572792e5c..9cf960e1a 100644
--- a/test/mobilizon/service/activity_pub/utils_test.exs
+++ b/test/mobilizon/service/activity_pub/utils_test.exs
@@ -5,7 +5,7 @@ defmodule Mobilizon.Service.ActivityPub.UtilsTest do
import Mobilizon.Factory
- alias Mobilizon.Service.ActivityPub.{Converter, Utils}
+ alias Mobilizon.Service.ActivityPub.Converter
alias MobilizonWeb.Endpoint
alias MobilizonWeb.Router.Helpers, as: Routes
@@ -36,7 +36,8 @@ defmodule Mobilizon.Service.ActivityPub.UtilsTest do
"uuid" => reply.uuid,
"id" => Routes.page_url(Endpoint, :comment, reply.uuid),
"inReplyTo" => comment.url,
- "attributedTo" => reply.actor.url
+ "attributedTo" => reply.actor.url,
+ "mediaType" => "text/html"
} == Converter.Comment.model_to_as(reply)
end
@@ -44,7 +45,7 @@ defmodule Mobilizon.Service.ActivityPub.UtilsTest do
comment = insert(:comment)
reply = insert(:comment, in_reply_to_comment: comment)
to = ["https://www.w3.org/ns/activitystreams#Public"]
- comment_data = Utils.make_comment_data(reply.actor.url, to, reply.text, comment.url)
+ comment_data = Converter.Comment.model_to_as(reply)
assert comment_data["type"] == "Note"
assert comment_data["to"] == to
assert comment_data["content"] == reply.text
diff --git a/test/mobilizon/service/formatter/formatter_test.exs b/test/mobilizon/service/formatter/formatter_test.exs
index c1194a4fc..4948543e9 100644
--- a/test/mobilizon/service/formatter/formatter_test.exs
+++ b/test/mobilizon/service/formatter/formatter_test.exs
@@ -33,21 +33,21 @@ defmodule Mobilizon.Service.FormatterTest do
text = "Hey, check out https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla ."
expected =
- "Hey, check out https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla ."
+ "Hey, check out https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla ."
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://mastodon.social/@lambadalambda"
expected =
- "https://mastodon.social/@lambadalambda"
+ "https://mastodon.social/@lambadalambda"
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://mastodon.social:4000/@lambadalambda"
expected =
- "https://mastodon.social:4000/@lambadalambda"
+ "https://mastodon.social:4000/@lambadalambda"
assert {^expected, [], []} = Formatter.linkify(text)
@@ -59,56 +59,57 @@ defmodule Mobilizon.Service.FormatterTest do
text = "http://www.cs.vu.nl/~ast/intel/"
expected =
- "http://www.cs.vu.nl/~ast/intel/"
+ "http://www.cs.vu.nl/~ast/intel/"
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://forum.zdoom.org/viewtopic.php?f=44&t=57087"
expected =
- "https://forum.zdoom.org/viewtopic.php?f=44&t=57087"
+ "https://forum.zdoom.org/viewtopic.php?f=44&t=57087"
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul"
expected =
- "https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul"
+ "https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul"
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://www.google.co.jp/search?q=Nasim+Aghdam"
expected =
- "https://www.google.co.jp/search?q=Nasim+Aghdam"
+ "https://www.google.co.jp/search?q=Nasim+Aghdam"
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://en.wikipedia.org/wiki/Duff's_device"
expected =
- "https://en.wikipedia.org/wiki/Duff's_device"
+ "https://en.wikipedia.org/wiki/Duff's_device"
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://pleroma.com https://pleroma.com/sucks"
expected =
- "https://pleroma.com https://pleroma.com/sucks"
+ "https://pleroma.com https://pleroma.com/sucks"
assert {^expected, [], []} = Formatter.linkify(text)
text = "xmpp:contact@hacktivis.me"
expected =
- "xmpp:contact@hacktivis.me"
+ "xmpp:contact@hacktivis.me"
assert {^expected, [], []} = Formatter.linkify(text)
text =
"magnet:?xt=urn:btih:7ec9d298e91d6e4394d1379caf073c77ff3e3136&tr=udp%3A%2F%2Fopentor.org%3A2710&tr=udp%3A%2F%2Ftracker.blackunicorn.xyz%3A6969&tr=udp%3A%2F%2Ftracker.ccc.de%3A80&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com"
- expected = "#{text}"
+ expected =
+ "#{text}"
assert {^expected, [], []} = Formatter.linkify(text)
end
@@ -117,32 +118,36 @@ defmodule Mobilizon.Service.FormatterTest do
describe "add_user_links" do
test "gives a replacement for user links, using local nicknames in user links text" do
text = "@gsimg According to @archa_eme_, that is @daggsy. Also hello @archaeme@archae.me"
- _gsimg = insert(:actor, preferred_username: "gsimg")
+ gsimg = insert(:actor, preferred_username: "gsimg")
- _archaeme =
+ archaeme =
insert(:actor, preferred_username: "archa_eme_", url: "https://archeme/@archa_eme_")
- _archaeme_remote = insert(:actor, preferred_username: "archaeme", domain: "archae.me")
+ archaeme_remote = insert(:actor, preferred_username: "archaeme", domain: "archae.me")
{text, mentions, []} = Formatter.linkify(text)
assert length(mentions) == 3
expected_text =
- "@gsimg According to @archa_eme_, that is @daggsy. Also hello @archaeme"
+ "@gsimg According to @archa_eme_, that is @daggsy. Also hello @archaeme"
assert expected_text == text
end
test "gives a replacement for single-character local nicknames" do
text = "@o hi"
- _o = insert(:actor, preferred_username: "o")
+ o = insert(:actor, preferred_username: "o")
{text, mentions, []} = Formatter.linkify(text)
assert length(mentions) == 1
- expected_text = "@o hi"
+ expected_text = "@o hi"
assert expected_text == text
end
diff --git a/test/mobilizon_web/api/report_test.exs b/test/mobilizon_web/api/report_test.exs
index e2e80c083..db7d3f9bd 100644
--- a/test/mobilizon_web/api/report_test.exs
+++ b/test/mobilizon_web/api/report_test.exs
@@ -14,7 +14,8 @@ defmodule MobilizonWeb.API.ReportTest do
describe "reports" do
test "creates a report on a event" do
- %Actor{id: reporter_id, url: reporter_url} = insert(:actor)
+ %Actor{url: relay_reporter_url} = Mobilizon.Service.ActivityPub.Relay.get_actor()
+ %Actor{id: reporter_id} = insert(:actor)
%Actor{id: reported_id, url: reported_url} = reported = insert(:actor)
%Event{id: event_id, url: event_url} = _event = insert(:event, organizer_actor: reported)
@@ -28,11 +29,11 @@ defmodule MobilizonWeb.API.ReportTest do
content: comment,
event_id: event_id,
comments_ids: [],
- local: true
+ forward: false
})
assert %Activity{
- actor: ^reporter_url,
+ actor: ^relay_reporter_url,
data: %{
"type" => "Flag",
"cc" => [],
@@ -43,7 +44,8 @@ defmodule MobilizonWeb.API.ReportTest do
end
test "creates a report on several comments" do
- %Actor{id: reporter_id, url: reporter_url} = insert(:actor)
+ %Actor{url: relay_reporter_url} = Mobilizon.Service.ActivityPub.Relay.get_actor()
+ %Actor{id: reporter_id} = insert(:actor)
%Actor{id: reported_id, url: reported_url} = reported = insert(:actor)
%Comment{id: comment_1_id, url: comment_1_url} =
@@ -64,20 +66,21 @@ defmodule MobilizonWeb.API.ReportTest do
})
assert %Activity{
- actor: ^reporter_url,
+ actor: ^relay_reporter_url,
data: %{
"type" => "Flag",
"content" => ^comment,
"object" => [^reported_url, ^comment_1_url, ^comment_2_url],
- "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "to" => [],
"cc" => [],
- "actor" => ^reporter_url
+ "actor" => ^relay_reporter_url
}
} = flag_activity
end
test "creates a report that gets federated" do
- %Actor{id: reporter_id, url: reporter_url} = insert(:actor)
+ %Actor{url: relay_reporter_url} = Mobilizon.Service.ActivityPub.Relay.get_actor()
+ %Actor{id: reporter_id} = insert(:actor)
%Actor{id: reported_id, url: reported_url} = reported = insert(:actor)
%Comment{id: comment_1_id, url: comment_1_url} =
@@ -96,21 +99,21 @@ defmodule MobilizonWeb.API.ReportTest do
content: comment,
event_id: nil,
comments_ids: [comment_1_id, comment_2_id],
- local: false
+ forward: true
})
assert %Activity{
- actor: ^reporter_url,
+ actor: ^relay_reporter_url,
data: %{
"type" => "Flag",
- "actor" => ^reporter_url,
+ "actor" => ^relay_reporter_url,
"cc" => [^reported_url],
"content" => ^encoded_comment,
"object" => [^reported_url, ^comment_1_url, ^comment_2_url],
- "to" => ["https://www.w3.org/ns/activitystreams#Public"]
+ "to" => []
},
local: true,
- recipients: ["https://www.w3.org/ns/activitystreams#Public", ^reported_url]
+ recipients: [^reported_url]
} = flag_activity
end
diff --git a/test/mobilizon_web/controllers/activity_pub_controller_test.exs b/test/mobilizon_web/controllers/activity_pub_controller_test.exs
index 30e1f67ae..6c1b9a3d2 100644
--- a/test/mobilizon_web/controllers/activity_pub_controller_test.exs
+++ b/test/mobilizon_web/controllers/activity_pub_controller_test.exs
@@ -19,6 +19,11 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
alias MobilizonWeb.PageView
alias MobilizonWeb.Router.Helpers, as: Routes
+ setup_all do
+ Mobilizon.Config.put([:instance, :federating], true)
+ :ok
+ end
+
setup do
conn = build_conn() |> put_req_header("accept", "application/activity+json")
{:ok, conn: conn}
@@ -34,7 +39,10 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
actor = Actors.get_actor!(actor.id)
- assert json_response(conn, 200) == ActorView.render("actor.json", %{actor: actor})
+ assert json_response(conn, 200) ==
+ ActorView.render("actor.json", %{actor: actor})
+ |> Jason.encode!()
+ |> Jason.decode!()
end
end
diff --git a/test/mobilizon_web/controllers/webfinger_controller_test.exs b/test/mobilizon_web/controllers/webfinger_controller_test.exs
index e97db6f4c..d980d1be9 100644
--- a/test/mobilizon_web/controllers/webfinger_controller_test.exs
+++ b/test/mobilizon_web/controllers/webfinger_controller_test.exs
@@ -9,6 +9,11 @@ defmodule MobilizonWeb.WebFingerTest do
alias Mobilizon.Service.WebFinger
import Mobilizon.Factory
+ setup_all do
+ Mobilizon.Config.put([:instance, :federating], true)
+ :ok
+ end
+
test "GET /.well-known/host-meta", %{conn: conn} do
conn = get(conn, "/.well-known/host-meta")
diff --git a/test/mobilizon_web/plugs/federating_plug_test.exs b/test/mobilizon_web/plugs/federating_plug_test.exs
new file mode 100644
index 000000000..74aa72ae5
--- /dev/null
+++ b/test/mobilizon_web/plugs/federating_plug_test.exs
@@ -0,0 +1,30 @@
+# Portions of this file are derived from Pleroma:
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule MobilizonWeb.Plug.FederatingTest do
+ use MobilizonWeb.ConnCase
+
+ test "returns and halt the conn when federating is disabled" do
+ Mobilizon.Config.put([:instance, :federating], false)
+
+ conn =
+ build_conn()
+ |> MobilizonWeb.Plugs.Federating.call(%{})
+
+ assert conn.status == 404
+ assert conn.halted
+ end
+
+ test "does nothing when federating is enabled" do
+ Mobilizon.Config.put([:instance, :federating], true)
+
+ conn =
+ build_conn()
+ |> MobilizonWeb.Plugs.Federating.call(%{})
+
+ refute conn.status
+ refute conn.halted
+ end
+end
diff --git a/test/mobilizon_web/plugs/mapped_identity_to_signature_plug_test.exs b/test/mobilizon_web/plugs/mapped_identity_to_signature_plug_test.exs
new file mode 100644
index 000000000..9af71da9d
--- /dev/null
+++ b/test/mobilizon_web/plugs/mapped_identity_to_signature_plug_test.exs
@@ -0,0 +1,60 @@
+# Portions of this file are derived from Pleroma:
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule MobilizonWeb.Plugs.MappedSignatureToIdentityPlugTest do
+ use MobilizonWeb.ConnCase
+ use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
+ alias MobilizonWeb.Plugs.MappedSignatureToIdentity
+
+ defp set_signature(conn, key_id) do
+ conn
+ |> put_req_header("signature", "keyId=\"#{key_id}\"")
+ |> assign(:valid_signature, true)
+ end
+
+ test "it successfully maps a valid identity with a valid signature" do
+ use_cassette "activity_pub/signature/valid" do
+ conn =
+ build_conn(:get, "/doesntmattter")
+ |> set_signature("https://framapiaf.org/users/admin")
+ |> MappedSignatureToIdentity.call(%{})
+
+ refute is_nil(conn.assigns.actor)
+ end
+ end
+
+ test "it successfully maps a valid identity with a valid signature with payload" do
+ use_cassette "activity_pub/signature/valid_payload" do
+ conn =
+ build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"})
+ |> set_signature("https://framapiaf.org/users/admin")
+ |> MappedSignatureToIdentity.call(%{})
+
+ refute is_nil(conn.assigns.actor)
+ end
+ end
+
+ test "it considers a mapped identity to be invalid when it mismatches a payload" do
+ use_cassette "activity_pub/signature/invalid_payload" do
+ conn =
+ build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"})
+ |> set_signature("https://niu.moe/users/rye")
+ |> MappedSignatureToIdentity.call(%{})
+
+ assert %{valid_signature: false} == conn.assigns
+ end
+ end
+
+ test "it considers a mapped identity to be invalid when the identity cannot be found" do
+ use_cassette "activity_pub/signature/invalid_not_found" do
+ conn =
+ build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"})
+ |> set_signature("http://niu.moe/users/rye")
+ |> MappedSignatureToIdentity.call(%{})
+
+ assert %{valid_signature: false} == conn.assigns
+ end
+ end
+end
diff --git a/test/mobilizon_web/resolvers/admin_resolver_test.exs b/test/mobilizon_web/resolvers/admin_resolver_test.exs
index a6a079b3b..7e6cf996e 100644
--- a/test/mobilizon_web/resolvers/admin_resolver_test.exs
+++ b/test/mobilizon_web/resolvers/admin_resolver_test.exs
@@ -121,4 +121,99 @@ defmodule MobilizonWeb.Resolvers.AdminResolverTest do
title
end
end
+
+ describe "Resolver: Get the list of relay followers" do
+ test "test list_relay_followers/3 returns relay followers", %{conn: conn} do
+ %User{} = user_admin = insert(:user, role: :administrator)
+
+ follower_actor =
+ insert(:actor,
+ domain: "localhost",
+ user: nil,
+ url: "http://localhost:8080/actor",
+ preferred_username: "instance_actor",
+ name: "I am an instance actor"
+ )
+
+ %Actor{} = relay_actor = Mobilizon.Service.ActivityPub.Relay.get_actor()
+ insert(:follower, actor: follower_actor, target_actor: relay_actor)
+
+ query = """
+ {
+ relayFollowers {
+ elements {
+ actor {
+ preferredUsername,
+ domain,
+ },
+ approved
+ },
+ total
+ }
+ }
+ """
+
+ res =
+ conn
+ |> auth_conn(user_admin)
+ |> AbsintheHelpers.graphql_query(query: query)
+
+ assert is_nil(res["errors"])
+
+ assert hd(res["data"]["relayFollowers"]["elements"]) == %{
+ "actor" => %{"preferredUsername" => "instance_actor", "domain" => "localhost"},
+ "approved" => false
+ }
+ end
+
+ test "test list_relay_followers/3 returns relay followings", %{conn: conn} do
+ %User{} = user_admin = insert(:user, role: :administrator)
+
+ %Actor{
+ preferred_username: following_actor_preferred_username,
+ domain: following_actor_domain
+ } =
+ following_actor =
+ insert(:actor,
+ domain: "localhost",
+ user: nil,
+ url: "http://localhost:8080/actor",
+ preferred_username: "instance_actor",
+ name: "I am an instance actor"
+ )
+
+ %Actor{} = relay_actor = Mobilizon.Service.ActivityPub.Relay.get_actor()
+ insert(:follower, actor: relay_actor, target_actor: following_actor)
+
+ query = """
+ {
+ relayFollowings {
+ elements {
+ targetActor {
+ preferredUsername,
+ domain,
+ },
+ approved
+ },
+ total
+ }
+ }
+ """
+
+ res =
+ conn
+ |> auth_conn(user_admin)
+ |> AbsintheHelpers.graphql_query(query: query)
+
+ assert is_nil(res["errors"])
+
+ assert hd(res["data"]["relayFollowings"]["elements"]) == %{
+ "targetActor" => %{
+ "preferredUsername" => following_actor_preferred_username,
+ "domain" => following_actor_domain
+ },
+ "approved" => false
+ }
+ end
+ end
end
diff --git a/test/mobilizon_web/resolvers/participant_resolver_test.exs b/test/mobilizon_web/resolvers/participant_resolver_test.exs
index 5e0639669..7aadfd4a5 100644
--- a/test/mobilizon_web/resolvers/participant_resolver_test.exs
+++ b/test/mobilizon_web/resolvers/participant_resolver_test.exs
@@ -199,7 +199,9 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
user: user,
actor: actor
} do
- event = insert(:event, %{organizer_actor: actor})
+ event =
+ insert(:event, %{organizer_actor: actor, participant_stats: %{creator: 1, participant: 1}})
+
insert(:participant, %{actor: actor, event: event, role: :creator})
user2 = insert(:user)
actor2 = insert(:actor, user: user2)
diff --git a/test/mobilizon_web/resolvers/person_resolver_test.exs b/test/mobilizon_web/resolvers/person_resolver_test.exs
index b8cd88d73..e0e7ae609 100644
--- a/test/mobilizon_web/resolvers/person_resolver_test.exs
+++ b/test/mobilizon_web/resolvers/person_resolver_test.exs
@@ -3,6 +3,7 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
alias MobilizonWeb.AbsintheHelpers
alias Mobilizon.Actors.Actor
import Mobilizon.Factory
+ use Oban.Testing, repo: Mobilizon.Storage.Repo
@non_existent_username "nonexistent"
@@ -478,7 +479,7 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
"Cannot remove the last administrator of a group"
end
- test "delete_person/3 should delete a user identity", context do
+ test "delete_person/3 should delete an actor identity", context do
user = insert(:user)
%Actor{id: person_id} = insert(:actor, user: user, preferred_username: "riri")
insert(:actor, user: user, preferred_username: "fifi")
@@ -498,6 +499,13 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
assert json_response(res, 200)["errors"] == nil
+ assert_enqueued(
+ worker: Mobilizon.Service.Workers.BackgroundWorker,
+ args: %{"actor_id" => person_id, "op" => "delete_actor"}
+ )
+
+ assert %{success: 1, failure: 0} == Oban.drain_queue(:background)
+
query = """
{
person(id: "#{person_id}") {
diff --git a/test/support/abinthe_helpers.ex b/test/support/abinthe_helpers.ex
index 33df27dac..d9c8a0c12 100644
--- a/test/support/abinthe_helpers.ex
+++ b/test/support/abinthe_helpers.ex
@@ -25,7 +25,7 @@ defmodule MobilizonWeb.AbsintheHelpers do
conn
|> post(
"/api",
- build_query(options[:query], options[:variables])
+ build_query(options[:query], Keyword.get(options, :variables, %{}))
)
|> json_response(200)
end
diff --git a/test/tasks/relay_test.exs b/test/tasks/relay_test.exs
index 26ee7cb2c..cdcedddb0 100644
--- a/test/tasks/relay_test.exs
+++ b/test/tasks/relay_test.exs
@@ -14,14 +14,14 @@ defmodule Mix.Tasks.Mobilizon.RelayTest do
describe "running follow" do
test "relay is followed" do
use_cassette "relay/fetch_relay_follow" do
- target_instance = "http://localhost:8080/actor"
+ target_instance = "mobilizon1.com"
Mix.Tasks.Mobilizon.Relay.run(["follow", target_instance])
local_actor = Relay.get_actor()
assert local_actor.url =~ "/relay"
- {:ok, target_actor} = Actors.get_actor_by_url(target_instance)
+ {:ok, target_actor} = Actors.get_actor_by_url("http://#{target_instance}/relay")
refute is_nil(target_actor.domain)
assert Actors.is_following(local_actor, target_actor)
@@ -32,12 +32,15 @@ defmodule Mix.Tasks.Mobilizon.RelayTest do
describe "running unfollow" do
test "relay is unfollowed" do
use_cassette "relay/fetch_relay_unfollow" do
- target_instance = "http://localhost:8080/actor"
+ target_instance = "mobilizon1.com"
Mix.Tasks.Mobilizon.Relay.run(["follow", target_instance])
%Actor{} = local_actor = Relay.get_actor()
- {:ok, %Actor{} = target_actor} = Actors.get_actor_by_url(target_instance)
+
+ {:ok, %Actor{} = target_actor} =
+ Actors.get_actor_by_url("http://#{target_instance}/relay")
+
assert %Follower{} = Actors.is_following(local_actor, target_actor)
Mix.Tasks.Mobilizon.Relay.run(["unfollow", target_instance])
|