ChatMessages: Better validation.

This commit is contained in:
lain 2020-04-16 15:21:47 +02:00
parent 3d4eca5dd4
commit e2ced04917
8 changed files with 128 additions and 6 deletions

View file

@ -31,7 +31,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
def validate(%{"type" => "ChatMessage"} = object, meta) do def validate(%{"type" => "ChatMessage"} = object, meta) do
with {:ok, object} <- with {:ok, object} <-
object object
|> ChatMessageValidator.cast_and_apply() do |> ChatMessageValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
object = stringify_keys(object) object = stringify_keys(object)
{:ok, object, meta} {:ok, object, meta}
end end
@ -40,7 +41,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
def validate(%{"type" => "Create"} = object, meta) do def validate(%{"type" => "Create"} = object, meta) do
with {:ok, object} <- with {:ok, object} <-
object object
|> CreateChatMessageValidator.cast_and_apply() do |> CreateChatMessageValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
object = stringify_keys(object) object = stringify_keys(object)
{:ok, object, meta} {:ok, object, meta}
end end

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
use Ecto.Schema use Ecto.Schema
alias Pleroma.Web.ActivityPub.ObjectValidators.Types alias Pleroma.Web.ActivityPub.ObjectValidators.Types
alias Pleroma.User
import Ecto.Changeset import Ecto.Changeset
@ -54,5 +55,30 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
data_cng data_cng
|> validate_inclusion(:type, ["ChatMessage"]) |> validate_inclusion(:type, ["ChatMessage"])
|> validate_required([:id, :actor, :to, :type, :content]) |> validate_required([:id, :actor, :to, :type, :content])
|> validate_length(:to, is: 1)
|> validate_local_concern()
end
@doc "Validates if at least one of the users in this ChatMessage is a local user, otherwise we don't want the message in our system. It also validates the presence of both users in our system."
def validate_local_concern(cng) do
with actor_ap <- get_field(cng, :actor),
{_, %User{} = actor} <- {:find_actor, User.get_cached_by_ap_id(actor_ap)},
{_, %User{} = recipient} <-
{:find_recipient, User.get_cached_by_ap_id(get_field(cng, :to) |> hd())},
{_, true} <- {:local?, Enum.any?([actor, recipient], & &1.local)} do
cng
else
{:local?, false} ->
cng
|> add_error(:actor, "actor and recipient are both remote")
{:find_actor, _} ->
cng
|> add_error(:actor, "can't find user")
{:find_recipient, _} ->
cng
|> add_error(:to, "can't find user")
end
end end
end end

View file

@ -8,7 +8,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.User alias Pleroma.User
def validate_actor_presence(cng, field_name \\ :actor) do def validate_actor_presence(cng) do
validate_user_presence(cng, :actor)
end
def validate_user_presence(cng, field_name) do
cng cng
|> validate_change(field_name, fn field_name, actor -> |> validate_change(field_name, fn field_name, actor ->
if User.get_cached_by_ap_id(actor) do if User.get_cached_by_ap_id(actor) do

View file

@ -32,4 +32,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator do
def cast_data(data) do def cast_data(data) do
cast(%__MODULE__{}, data, __schema__(:fields)) cast(%__MODULE__{}, data, __schema__(:fields))
end end
# No validation yet
def cast_and_validate(data) do
cast_data(data)
end
end end

View file

@ -25,6 +25,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.ChatMessageHandling do
{_, {:ok, activity, _meta}} <- {_, {:ok, activity, _meta}} <-
{:common_pipeline, Pipeline.common_pipeline(cast_data, local: false)} do {:common_pipeline, Pipeline.common_pipeline(cast_data, local: false)} do
{:ok, activity} {:ok, activity}
else
e ->
{:error, e}
end end
end end
end end

View file

@ -3,7 +3,7 @@
"id": "http://2hu.gensokyo/objects/1", "id": "http://2hu.gensokyo/objects/1",
"object": { "object": {
"attributedTo": "http://2hu.gensokyo/users/raymoo", "attributedTo": "http://2hu.gensokyo/users/raymoo",
"content": "You expected a cute girl? Too bad.", "content": "You expected a cute girl? Too bad. <script>alert('XSS')</script>",
"id": "http://2hu.gensokyo/objects/2", "id": "http://2hu.gensokyo/objects/2",
"published": "2020-02-12T14:08:20Z", "published": "2020-02-12T14:08:20Z",
"to": [ "to": [

View file

@ -5,9 +5,61 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.ActivityPub.Builder
import Pleroma.Factory import Pleroma.Factory
describe "chat messages" do
setup do
user = insert(:user)
recipient = insert(:user, local: false)
{:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey")
%{user: user, recipient: recipient, valid_chat_message: valid_chat_message}
end
test "validates for a basic object we build", %{valid_chat_message: valid_chat_message} do
assert {:ok, _object, _meta} = ObjectValidator.validate(valid_chat_message, [])
end
test "does not validate if the actor or the recipient is not in our system", %{
valid_chat_message: valid_chat_message
} do
chat_message =
valid_chat_message
|> Map.put("actor", "https://raymoo.com/raymoo")
{:error, _} = ObjectValidator.validate(chat_message, [])
chat_message =
valid_chat_message
|> Map.put("to", ["https://raymoo.com/raymoo"])
{:error, _} = ObjectValidator.validate(chat_message, [])
end
test "does not validate for a message with multiple recipients", %{
valid_chat_message: valid_chat_message,
user: user,
recipient: recipient
} do
chat_message =
valid_chat_message
|> Map.put("to", [user.ap_id, recipient.ap_id])
assert {:error, _} = ObjectValidator.validate(chat_message, [])
end
test "does not validate if it doesn't concern local users" do
user = insert(:user, local: false)
recipient = insert(:user, local: false)
{:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey")
assert {:error, _} = ObjectValidator.validate(valid_chat_message, [])
end
end
describe "likes" do describe "likes" do
setup do setup do
user = insert(:user) user = insert(:user)

View file

@ -12,13 +12,43 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.ChatMessageTest do
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
describe "handle_incoming" do describe "handle_incoming" do
test "it insert it" do test "it rejects messages that don't contain content" do
data =
File.read!("test/fixtures/create-chat-message.json")
|> Poison.decode!()
object =
data["object"]
|> Map.delete("content")
data =
data
|> Map.put("object", object)
_author = insert(:user, ap_id: data["actor"], local: false)
_recipient = insert(:user, ap_id: List.first(data["to"]), local: true)
{:error, _} = Transmogrifier.handle_incoming(data)
end
test "it rejects messages that don't concern local users" do
data =
File.read!("test/fixtures/create-chat-message.json")
|> Poison.decode!()
_author = insert(:user, ap_id: data["actor"], local: false)
_recipient = insert(:user, ap_id: List.first(data["to"]), local: false)
{:error, _} = Transmogrifier.handle_incoming(data)
end
test "it inserts it and creates a chat" do
data = data =
File.read!("test/fixtures/create-chat-message.json") File.read!("test/fixtures/create-chat-message.json")
|> Poison.decode!() |> Poison.decode!()
author = insert(:user, ap_id: data["actor"], local: false) author = insert(:user, ap_id: data["actor"], local: false)
recipient = insert(:user, ap_id: List.first(data["to"]), local: false) recipient = insert(:user, ap_id: List.first(data["to"]), local: true)
{:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(data) {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(data)