mobilizon/lib/web/controllers/activity_pub_controller.ex

244 lines
7.0 KiB
Elixir

# Portions of this file are derived from Pleroma:
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social>
# SPDX-License-Identifier: AGPL-3.0-only
# Upstream: https://git.pleroma.social/pleroma/pleroma/blob/develop/lib/pleroma/web/activity_pub/activity_pub_controller.ex
defmodule Mobilizon.Web.ActivityPubController do
use Mobilizon.Web, :controller
alias Mobilizon.{Actors, Config}
alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.{Federator, Utils}
alias Mobilizon.Web.ActivityPub.ActorView
alias Mobilizon.Web.Cache
alias Plug.Conn
require Logger
action_fallback(:errors)
plug(Mobilizon.Web.Plugs.Federating when action in [:inbox, :relay])
plug(:relay_active? when action in [:relay])
@spec relay_active?(Plug.Conn.t(), any()) :: Plug.Conn.t()
def relay_active?(conn, _) do
if Config.get([:instance, :allow_relay]) do
conn
else
conn
|> put_status(404)
|> json("Not found")
|> halt()
end
end
@spec following(Plug.Conn.t(), map()) :: Plug.Conn.t()
def following(conn, args) do
actor_collection(conn, "following", args)
end
@spec followers(Plug.Conn.t(), map()) :: Plug.Conn.t()
def followers(conn, args) do
actor_collection(conn, "followers", args)
end
@spec members(Plug.Conn.t(), map()) :: Plug.Conn.t()
def members(conn, args) do
actor_collection(conn, "members", args)
end
@spec resources(Plug.Conn.t(), map()) :: Plug.Conn.t()
def resources(conn, args) do
actor_collection(conn, "resources", args)
end
@spec posts(Plug.Conn.t(), map()) :: Plug.Conn.t()
def posts(conn, args) do
actor_collection(conn, "posts", args)
end
@spec todos(Plug.Conn.t(), map()) :: Plug.Conn.t()
def todos(conn, args) do
actor_collection(conn, "todos", args)
end
@spec events(Plug.Conn.t(), map()) :: Plug.Conn.t()
def events(conn, args) do
actor_collection(conn, "events", args)
end
@spec discussions(Plug.Conn.t(), map()) :: Plug.Conn.t()
def discussions(conn, args) do
actor_collection(conn, "discussions", args)
end
@ok_statuses [:ok, :commit]
@spec member(Plug.Conn.t(), map) :: {:error, :not_found} | Plug.Conn.t()
def member(conn, %{"uuid" => uuid}) do
case Cache.get_member_by_uuid_with_preload(uuid) do
{status, %Member{parent: %Actor{} = group, actor: %Actor{domain: nil} = _actor} = member}
when status in @ok_statuses ->
actor = Map.get(conn.assigns, :actor)
if actor_applicant_group_member?(group, actor) do
conn
|> put_resp_content_type("application/activity+json")
|> json(
ActorView.render("member.json", %{
member: member,
actor_applicant: actor
})
)
else
not_found(conn)
end
{status, %Member{actor: %Actor{url: domain}, parent: %Actor{} = group, url: url}}
when status in @ok_statuses and not is_nil(domain) ->
actor = Map.get(conn.assigns, :actor)
if actor_applicant_group_member?(group, actor) do
redirect(conn, external: url)
else
not_found(conn)
end
_ ->
not_found(conn)
end
end
@spec not_found(Plug.Conn.t()) :: Plug.Conn.t()
defp not_found(conn) do
conn
|> put_status(404)
|> json("Not found")
end
def outbox(conn, args) do
actor_collection(conn, "outbox", args)
end
def inbox(%{assigns: %{valid_signature: true}} = conn, %{"name" => preferred_username} = params) do
with %Actor{url: recipient_url} = recipient <-
Actors.get_local_actor_by_name(preferred_username),
{:ok, %Actor{} = actor} <- ActivityPubActor.get_or_fetch_actor_by_url(params["actor"]),
true <- Utils.recipient_in_message(recipient, actor, params),
params <- Utils.maybe_splice_recipient(recipient_url, params) do
Federator.enqueue(:incoming_ap_doc, params)
json(conn, "ok")
end
end
def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
Logger.debug("Got something with valid signature inside inbox")
Federator.enqueue(:incoming_ap_doc, params)
json(conn, "ok")
end
# only accept relayed Creates
def inbox(conn, %{"type" => "Create"} = params) do
Logger.debug(
"Signature missing or not from author, relayed Create message, fetching object from source"
)
ActivityPub.fetch_object_from_url(params["object"]["id"])
json(conn, "ok")
end
def inbox(conn, params) do
headers = Enum.into(conn.req_headers, %{})
if headers["signature"] && params["actor"] &&
String.contains?(headers["signature"], params["actor"]) do
Logger.debug(
"Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
)
Logger.debug(inspect(conn.req_headers))
end
json(conn, "error")
end
def relay(conn, _params) do
with {status, %Actor{} = actor} when status in [:commit, :ok] <- Cache.get_relay() do
conn
|> put_resp_content_type("application/activity+json")
|> json(ActorView.render("actor.json", %{actor: actor}))
end
end
def errors(conn, {:error, :not_found}) do
send_resp(conn, 404, "Not found")
end
def errors(conn, {:error, :bad_request}) do
send_resp(conn, 400, "Bad request")
end
def errors(conn, e) do
Logger.debug(inspect(e))
send_resp(conn, 500, "Unknown Error")
end
@spec actor_collection(Conn.t(), String.t(), map()) :: Conn.t()
defp actor_collection(conn, collection, %{"name" => name, "page" => page}) do
with {:page, {page, ""}} <- {:page, Integer.parse(page)},
page <- max(page, 1),
{:actor, %Actor{} = actor} <- {:actor, Actors.get_local_actor_by_name_with_preload(name)} do
conn
|> put_resp_content_type("application/activity+json")
|> json(
ActorView.render("#{collection}.json", %{
actor: actor,
page: page,
actor_applicant: Map.get(conn.assigns, :actor)
})
)
else
{:page, _} ->
{:error, :bad_request}
{:actor, _} ->
{:error, :not_found}
end
end
defp actor_collection(conn, collection, %{"name" => name}) do
case Actors.get_local_actor_by_name_with_preload(name) do
%Actor{} = actor ->
conn
|> put_resp_content_type("application/activity+json")
|> json(
ActorView.render("#{collection}.json", %{
actor: actor,
actor_applicant: Map.get(conn.assigns, :actor)
})
)
_ ->
{:error, :not_found}
end
end
@spec actor_applicant_group_member?(Actor.t(), Actor.t() | nil) :: boolean()
defp actor_applicant_group_member?(%Actor{}, nil), do: false
defp actor_applicant_group_member?(%Actor{id: group_id}, %Actor{id: actor_applicant_id}),
do:
Actors.get_member(actor_applicant_id, group_id, [
:member,
:moderator,
:administrator,
:creator
]) != {:error, :member_not_found}
end