# Akkoma: Magically expressive social media # Copyright © 2024 Akkoma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ObjectValidators.UserValidator do @moduledoc """ Checks whether ActivityPub data represents a valid user Users don't go through the same ingest pipeline like activities or other objects. To ensure this can only match a user and no users match in the other pipeline, this is a separate from the generic ObjectValidator. """ @behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating alias Pleroma.Object.Containment alias Pleroma.Signature require Pleroma.Constants @impl true def validate(object, meta) def validate(%{"type" => type, "id" => _id} = data, meta) when type in Pleroma.Constants.actor_types() do with :ok <- validate_pubkey(data), :ok <- validate_inbox(data), :ok <- contain_collection_origin(data) do {:ok, data, meta} else {:error, e} -> {:error, e} e -> {:error, e} end end def validate(_, _), do: {:error, "Not a user object"} defp mabye_validate_owner(nil, _actor), do: :ok defp mabye_validate_owner(actor, actor), do: :ok defp mabye_validate_owner(_owner, _actor), do: :error defp validate_pubkey( %{"id" => id, "publicKey" => %{"id" => pk_id, "publicKeyPem" => _key}} = data ) when id != nil do with {_, {:ok, kactor}} <- {:key, Signature.key_id_to_actor_id(pk_id)}, true <- id == kactor, :ok <- mabye_validate_owner(Map.get(data, "owner"), id) do :ok else {:key, _} -> {:error, "Unable to determine actor id from key id"} false -> {:error, "Key id does not relate to user id"} _ -> {:error, "Actor does not own its public key"} end end # pubkey is optional atm defp validate_pubkey(_data), do: :ok defp validate_inbox(%{"id" => id, "inbox" => inbox}) do case Containment.same_origin(id, inbox) do :ok -> :ok :error -> {:error, "Inbox on different doamin"} end end defp validate_inbox(_), do: {:error, "No inbox"} defp check_field_value(%{"id" => id} = _data, value) do Containment.same_origin(id, value) end defp maybe_check_field(data, field) do with val when val != nil <- data[field], :ok <- check_field_value(data, val) do :ok else nil -> :ok _ -> {:error, "#{field} on different domain"} end end defp contain_collection_origin(data) do Enum.reduce(["followers", "following", "featured"], :ok, fn field, :ok -> maybe_check_field(data, field) _, error -> error end) end end