Merge branch 'features/ingestion-ecto-tag' into 'develop'

TagValidator: New

See merge request pleroma/pleroma!3244
This commit is contained in:
rinpatch 2021-04-16 09:55:55 +00:00
commit 15f87cf658
24 changed files with 166 additions and 69 deletions

View file

@ -114,9 +114,34 @@ def validate(
end
end
def validate(%{"type" => type} = object, meta)
when type in ~w[Event Question Audio Video Article] do
validator =
case type do
"Event" -> EventValidator
"Question" -> QuestionValidator
"Audio" -> AudioVideoValidator
"Video" -> AudioVideoValidator
"Article" -> ArticleNoteValidator
end
with {:ok, object} <-
object
|> validator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
object = stringify_keys(object)
# Insert copy of hashtags as strings for the non-hashtag table indexing
tag = (object["tag"] || []) ++ Object.hashtags(%Object{data: object})
object = Map.put(object, "tag", tag)
{:ok, object, meta}
end
end
def validate(%{"type" => type} = object, meta)
when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
Event ChatMessage Question Audio Video Article Answer] do
ChatMessage Answer] do
validator =
case type do
"Accept" -> AcceptRejectValidator
@ -126,12 +151,7 @@ def validate(%{"type" => type} = object, meta)
"Like" -> LikeValidator
"EmojiReact" -> EmojiReactValidator
"Announce" -> AnnounceValidator
"Event" -> EventValidator
"ChatMessage" -> ChatMessageValidator
"Question" -> QuestionValidator
"Audio" -> AudioVideoValidator
"Video" -> AudioVideoValidator
"Article" -> ArticleNoteValidator
"Answer" -> AnswerValidator
end
@ -180,7 +200,7 @@ def cast_and_apply(%{"type" => "Article"} = object) do
def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
# is_struct/1 isn't present in Elixir 1.8.x
# is_struct/1 appears in Elixir 1.11
def stringify_keys(%{__struct__: _} = object) do
object
|> Map.from_struct()

View file

@ -27,7 +27,7 @@ def cast_data(data) do
|> cast(data, __schema__(:fields))
end
def validate_data(cng) do
defp validate_data(cng) do
cng
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|> validate_inclusion(:type, ["Accept", "Reject"])

View file

@ -50,7 +50,7 @@ def fix_after_cast(cng) do
cng
end
def validate_data(data_cng) do
defp validate_data(data_cng) do
data_cng
|> validate_inclusion(:type, ["Announce"])
|> validate_required([:id, :type, :object, :actor, :to, :cc])

View file

@ -50,7 +50,7 @@ def changeset(struct, data) do
|> cast(data, __schema__(:fields))
end
def validate_data(data_cng) do
defp validate_data(data_cng) do
data_cng
|> validate_inclusion(:type, ["Answer"])
|> validate_required([:id, :inReplyTo, :name, :attributedTo, :actor])

View file

@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
alias Pleroma.Web.ActivityPub.Transmogrifier
import Ecto.Changeset
@ -22,8 +23,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do
field(:cc, ObjectValidators.Recipients, default: [])
field(:bto, ObjectValidators.Recipients, default: [])
field(:bcc, ObjectValidators.Recipients, default: [])
# TODO: Write type
field(:tag, {:array, :map}, default: [])
embeds_many(:tag, TagValidator)
field(:type, :string)
field(:name, :string)
@ -90,11 +90,12 @@ def changeset(struct, data) do
data = fix(data)
struct
|> cast(data, __schema__(:fields) -- [:attachment])
|> cast(data, __schema__(:fields) -- [:attachment, :tag])
|> cast_embed(:attachment)
|> cast_embed(:tag)
end
def validate_data(data_cng) do
defp validate_data(data_cng) do
data_cng
|> validate_inclusion(:type, ["Article", "Note"])
|> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])

View file

@ -6,7 +6,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator
import Ecto.Changeset
@ -90,7 +89,7 @@ defp fix_url(data) do
end
end
def validate_data(cng) do
defp validate_data(cng) do
cng
|> validate_inclusion(:type, ~w[Document Audio Image Video])
|> validate_required([:mediaType, :url, :type])

View file

@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
alias Pleroma.Web.ActivityPub.Transmogrifier
import Ecto.Changeset
@ -23,8 +24,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
field(:cc, ObjectValidators.Recipients, default: [])
field(:bto, ObjectValidators.Recipients, default: [])
field(:bcc, ObjectValidators.Recipients, default: [])
# TODO: Write type
field(:tag, {:array, :map}, default: [])
embeds_many(:tag, TagValidator)
field(:type, :string)
field(:name, :string)
@ -132,11 +132,12 @@ def changeset(struct, data) do
data = fix(data)
struct
|> cast(data, __schema__(:fields) -- [:attachment])
|> cast(data, __schema__(:fields) -- [:attachment, :tag])
|> cast_embed(:attachment)
|> cast_embed(:tag)
end
def validate_data(data_cng) do
defp validate_data(data_cng) do
data_cng
|> validate_inclusion(:type, ["Audio", "Video"])
|> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment])

View file

@ -26,7 +26,7 @@ def cast_data(data) do
|> cast(data, __schema__(:fields))
end
def validate_data(cng) do
defp validate_data(cng) do
cng
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|> validate_inclusion(:type, ["Block"])

View file

@ -67,7 +67,7 @@ def changeset(struct, data) do
|> cast_embed(:attachment)
end
def validate_data(data_cng) do
defp validate_data(data_cng) do
data_cng
|> validate_inclusion(:type, ["ChatMessage"])
|> validate_required([:id, :actor, :to, :type, :published])

View file

@ -39,7 +39,7 @@ def cast_and_validate(data, meta \\ []) do
|> validate_data(meta)
end
def validate_data(cng, meta \\ []) do
defp validate_data(cng, meta) do
cng
|> validate_required([:id, :actor, :to, :type, :object])
|> validate_inclusion(:type, ["Create"])

View file

@ -79,7 +79,7 @@ defp fix(data, meta) do
|> CommonFixes.fix_actor()
end
def validate_data(cng, meta \\ []) do
defp validate_data(cng, meta) do
cng
|> validate_required([:actor, :type, :object])
|> validate_inclusion(:type, ["Create"])

View file

@ -53,7 +53,7 @@ def add_deleted_activity_id(cng) do
Tombstone
Video
}
def validate_data(cng) do
defp validate_data(cng) do
cng
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|> validate_inclusion(:type, ["Delete"])

View file

@ -70,7 +70,7 @@ def validate_emoji(cng) do
end
end
def validate_data(data_cng) do
defp validate_data(data_cng) do
data_cng
|> validate_inclusion(:type, ["EmojiReact"])
|> validate_required([:id, :type, :object, :actor, :context, :to, :cc, :content])

View file

@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
alias Pleroma.Web.ActivityPub.Transmogrifier
import Ecto.Changeset
@ -23,8 +24,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
field(:cc, ObjectValidators.Recipients, default: [])
field(:bto, ObjectValidators.Recipients, default: [])
field(:bcc, ObjectValidators.Recipients, default: [])
# TODO: Write type
field(:tag, {:array, :map}, default: [])
embeds_many(:tag, TagValidator)
field(:type, :string)
field(:name, :string)
@ -81,11 +81,12 @@ def changeset(struct, data) do
data = fix(data)
struct
|> cast(data, __schema__(:fields) -- [:attachment])
|> cast(data, __schema__(:fields) -- [:attachment, :tag])
|> cast_embed(:attachment)
|> cast_embed(:tag)
end
def validate_data(data_cng) do
defp validate_data(data_cng) do
data_cng
|> validate_inclusion(:type, ["Event"])
|> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])

View file

@ -27,7 +27,7 @@ def cast_data(data) do
|> cast(data, __schema__(:fields))
end
def validate_data(cng) do
defp validate_data(cng) do
cng
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|> validate_inclusion(:type, ["Follow"])

View file

@ -76,7 +76,7 @@ def fix_recipients(cng) do
end
end
def validate_data(data_cng) do
defp validate_data(data_cng) do
data_cng
|> validate_inclusion(:type, ["Like"])
|> validate_required([:id, :type, :object, :actor, :context, :to, :cc])

View file

@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
alias Pleroma.Web.ActivityPub.Transmogrifier
import Ecto.Changeset
@ -24,8 +25,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
field(:cc, ObjectValidators.Recipients, default: [])
field(:bto, ObjectValidators.Recipients, default: [])
field(:bcc, ObjectValidators.Recipients, default: [])
# TODO: Write type
field(:tag, {:array, :map}, default: [])
embeds_many(:tag, TagValidator)
field(:type, :string)
field(:content, :string)
field(:context, :string)
@ -93,13 +93,14 @@ def changeset(struct, data) do
data = fix(data)
struct
|> cast(data, __schema__(:fields) -- [:anyOf, :oneOf, :attachment])
|> cast(data, __schema__(:fields) -- [:anyOf, :oneOf, :attachment, :tag])
|> cast_embed(:attachment)
|> cast_embed(:anyOf)
|> cast_embed(:oneOf)
|> cast_embed(:tag)
end
def validate_data(data_cng) do
defp validate_data(data_cng) do
data_cng
|> validate_inclusion(:type, ["Question"])
|> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])

View file

@ -0,0 +1,77 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
import Ecto.Changeset
@primary_key false
embedded_schema do
# Common
field(:type, :string)
field(:name, :string)
# Mention, Hashtag
field(:href, ObjectValidators.Uri)
# Emoji
embeds_one :icon, IconObjectValidator, primary_key: false do
field(:type, :string)
field(:url, ObjectValidators.Uri)
end
field(:updated, ObjectValidators.DateTime)
field(:id, ObjectValidators.Uri)
end
def cast_and_validate(data) do
data
|> cast_data()
end
def cast_data(data) do
%__MODULE__{}
|> changeset(data)
end
def changeset(struct, %{"type" => "Mention"} = data) do
struct
|> cast(data, [:type, :name, :href])
|> validate_required([:type, :href])
end
def changeset(struct, %{"type" => "Hashtag", "name" => name} = data) do
name =
cond do
"#" <> name -> name
name -> name
end
|> String.downcase()
data = Map.put(data, "name", name)
struct
|> cast(data, [:type, :name, :href])
|> validate_required([:type, :name])
end
def changeset(struct, %{"type" => "Emoji"} = data) do
data = Map.put(data, "name", String.trim(data["name"], ":"))
struct
|> cast(data, [:type, :name, :updated, :id])
|> cast_embed(:icon, with: &icon_changeset/2)
|> validate_required([:type, :name, :icon])
end
def icon_changeset(struct, data) do
struct
|> cast(data, [:type, :url])
|> validate_inclusion(:type, ~w[Image])
|> validate_required([:type, :url])
end
end

View file

@ -38,7 +38,7 @@ def changeset(struct, data) do
|> cast(data, __schema__(:fields))
end
def validate_data(data_cng) do
defp validate_data(data_cng) do
data_cng
|> validate_inclusion(:type, ["Undo"])
|> validate_required([:id, :type, :object, :actor, :to, :cc])

View file

@ -28,7 +28,7 @@ def cast_data(data) do
|> cast(data, __schema__(:fields))
end
def validate_data(cng) do
defp validate_data(cng) do
cng
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|> validate_inclusion(:type, ["Update"])

View file

@ -40,19 +40,17 @@ def common_pipeline(object, meta) do
end
end
def do_common_pipeline(object, meta) do
with {_, {:ok, validated_object, meta}} <-
{:validate_object, @object_validator.validate(object, meta)},
{_, {:ok, mrfd_object, meta}} <-
{:mrf_object, @mrf.pipeline_filter(validated_object, meta)},
{_, {:ok, activity, meta}} <-
{:persist_object, @activity_pub.persist(mrfd_object, meta)},
{_, {:ok, activity, meta}} <-
{:execute_side_effects, @side_effects.handle(activity, meta)},
{_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do
{:ok, activity, meta}
def do_common_pipeline(%{__struct__: _}, _meta), do: {:error, :is_struct}
def do_common_pipeline(message, meta) do
with {_, {:ok, message, meta}} <- {:validate, @object_validator.validate(message, meta)},
{_, {:ok, message, meta}} <- {:mrf, @mrf.pipeline_filter(message, meta)},
{_, {:ok, message, meta}} <- {:persist, @activity_pub.persist(message, meta)},
{_, {:ok, message, meta}} <- {:side_effects, @side_effects.handle(message, meta)},
{_, {:ok, _}} <- {:federation, maybe_federate(message, meta)} do
{:ok, message, meta}
else
{:mrf_object, {:reject, message, _}} -> {:reject, message}
{:mrf, {:reject, message, _}} -> {:reject, message}
e -> {:error, e}
end
end

View file

@ -564,7 +564,7 @@ def handle_incoming(
Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
else
{:error, {:validate_object, _}} = e ->
{:error, {:validate, _}} = e ->
# Check if we have a create activity for this
with {:ok, object_id} <- ObjectValidators.ObjectID.cast(data["object"]),
%Activity{data: %{"actor" => actor}} <-

View file

@ -228,17 +228,7 @@ def favorite_helper(user, id) do
{:find_object, _} ->
{:error, :not_found}
{:common_pipeline,
{
:error,
{
:validate_object,
{
:error,
changeset
}
}
}} = e ->
{:common_pipeline, {:error, {:validate, {:error, changeset}}}} = e ->
if {:object, {"already liked by this actor", []}} in changeset.errors do
{:ok, :already_liked}
else

View file

@ -25,9 +25,6 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do
MRFMock
|> expect(:pipeline_filter, fn o, m -> {:ok, o, m} end)
ActivityPubMock
|> expect(:persist, fn o, m -> {:ok, o, m} end)
SideEffectsMock
|> expect(:handle, fn o, m -> {:ok, o, m} end)
|> expect(:handle_after_transaction, fn m -> m end)
@ -42,6 +39,9 @@ test "when given an `object_data` in meta, Federation will receive a the origina
activity_with_object = %{activity | data: Map.put(activity.data, "object", object)}
ActivityPubMock
|> expect(:persist, fn _, m -> {:ok, activity, m} end)
FederatorMock
|> expect(:publish, fn ^activity_with_object -> :ok end)
@ -50,7 +50,7 @@ test "when given an `object_data` in meta, Federation will receive a the origina
assert {:ok, ^activity, ^meta} =
Pleroma.Web.ActivityPub.Pipeline.common_pipeline(
activity,
activity.data,
meta
)
end
@ -59,6 +59,9 @@ test "it goes through validation, filtering, persisting, side effects and federa
activity = insert(:note_activity)
meta = [local: true]
ActivityPubMock
|> expect(:persist, fn _, m -> {:ok, activity, m} end)
FederatorMock
|> expect(:publish, fn ^activity -> :ok end)
@ -66,29 +69,35 @@ test "it goes through validation, filtering, persisting, side effects and federa
|> expect(:get, fn [:instance, :federating] -> true end)
assert {:ok, ^activity, ^meta} =
Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity.data, meta)
end
test "it goes through validation, filtering, persisting, side effects without federation for remote activities" do
activity = insert(:note_activity)
meta = [local: false]
ActivityPubMock
|> expect(:persist, fn _, m -> {:ok, activity, m} end)
ConfigMock
|> expect(:get, fn [:instance, :federating] -> true end)
assert {:ok, ^activity, ^meta} =
Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity.data, meta)
end
test "it goes through validation, filtering, persisting, side effects without federation for local activities if federation is deactivated" do
activity = insert(:note_activity)
meta = [local: true]
ActivityPubMock
|> expect(:persist, fn _, m -> {:ok, activity, m} end)
ConfigMock
|> expect(:get, fn [:instance, :federating] -> false end)
assert {:ok, ^activity, ^meta} =
Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity.data, meta)
end
end
end