From d18ba133b2db3d6af05cce191c5ea0c200b57346 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Thu, 28 Jan 2021 09:33:30 +0100 Subject: [PATCH] Groups: Basic group validation. --- lib/pleroma/group.ex | 11 ++++++ lib/pleroma/user.ex | 4 +++ lib/pleroma/web/activity_pub/builder.ex | 25 ++++++++++++++ lib/pleroma/web/activity_pub/object_validator.ex | 11 ++++++ .../object_validators/group_validator.ex | 40 ++++++++++++++++++++++ lib/pleroma/web/common_api.ex | 7 ++++ .../migrations/20210113150220_create_groups.exs | 1 - test/pleroma/group_test.exs | 26 ++++++++++++++ .../object_validators/group_validation_test.exs | 24 +++++++++++++ .../web/common_api/group_messaging_test.exs | 25 ++++++++++++++ 10 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 lib/pleroma/web/activity_pub/object_validators/group_validator.ex create mode 100644 test/pleroma/web/activity_pub/object_validators/group_validation_test.exs create mode 100644 test/pleroma/web/common_api/group_messaging_test.exs diff --git a/lib/pleroma/group.ex b/lib/pleroma/group.ex index 732791ec2..880cd52c6 100644 --- a/lib/pleroma/group.ex +++ b/lib/pleroma/group.ex @@ -92,4 +92,15 @@ defmodule Pleroma.Group do {:ok, group} end end + + @spec get_for_object(map()) :: t() | nil + def get_for_object(%{"type" => "Group", "id" => id}) do + with %User{} = user <- User.get_cached_by_ap_id(id), + group <- Repo.preload(user, :group).group do + group + end + end + + def get_for_object(%{"type" => "Create", "object" => object}), do: get_for_object(object) + def get_for_object(_), do: nil end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 1e5c87403..5d9016e96 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -18,6 +18,7 @@ defmodule Pleroma.User do alias Pleroma.Emoji alias Pleroma.FollowingRelationship alias Pleroma.Formatter + alias Pleroma.Group alias Pleroma.HTML alias Pleroma.Keys alias Pleroma.MFA @@ -209,6 +210,9 @@ defmodule Pleroma.User do on_replace: :delete ) + # Some `users` are actually groups. In this case, they can have a corresponding `Group` + has_one(:group, Group) + timestamps() end diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index f56bfc600..08f017a51 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -12,6 +12,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do alias Pleroma.Emoji alias Pleroma.Object alias Pleroma.User + alias Pleroma.Web alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility @@ -105,6 +106,30 @@ defmodule Pleroma.Web.ActivityPub.Builder do }, []} end + def group(owner, name \\ nil, description \\ nil) do + id = Ecto.UUID.generate() + ap_id = "#{Web.base_url()}/groups/#{id}" + + {:ok, + %{ + "id" => ap_id, + "type" => "Group", + "name" => name, + "summary" => description, + "following" => "#{ap_id}/following", + "followers" => "#{ap_id}/followers", + "members" => "#{ap_id}/members", + # attributedTo? owner? admin? + "attributedTo" => owner.ap_id + }, []} + end + + def create_group(owner, params \\ %{}) do + with {:ok, group, _} <- group(owner, params[:name], params[:description]) do + create(owner, group, []) + end + end + def create(actor, object, recipients) do context = if is_map(object) do diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 297c19cc0..264ae5831 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -29,6 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator alias Pleroma.Web.ActivityPub.ObjectValidators.EventValidator alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.GroupValidator alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator @@ -37,6 +38,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do @impl true def validate(object, meta) + def validate(%{"type" => "Group"} = object, meta) do + with {:ok, object} <- + object + |> GroupValidator.cast_and_validate() + |> Ecto.Changeset.apply_action(:insert) do + object = stringify_keys(object) + {:ok, object, meta} + end + end + def validate(%{"type" => type} = object, meta) when type in ~w[Accept Reject] do with {:ok, object} <- diff --git a/lib/pleroma/web/activity_pub/object_validators/group_validator.ex b/lib/pleroma/web/activity_pub/object_validators/group_validator.ex new file mode 100644 index 000000000..cf7ae36c5 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/group_validator.ex @@ -0,0 +1,40 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.GroupValidator do + use Ecto.Schema + + alias Pleroma.EctoType.ActivityPub.ObjectValidators + + import Ecto.Changeset + + @primary_key false + + embedded_schema do + field(:id, ObjectValidators.ObjectID, primary_key: true) + field(:type, :string) + end + + def cast_and_validate(data) do + data + |> cast_data() + |> validate_data() + end + + def cast_data(data) do + %__MODULE__{} + |> changeset(data) + end + + def changeset(struct, data) do + struct + |> cast(data, __schema__(:fields)) + end + + def validate_data(data_cng) do + data_cng + |> validate_inclusion(:type, ["Group"]) + |> validate_required([:id]) + end +end diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index b003e30c7..400fe00c9 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -30,6 +30,13 @@ defmodule Pleroma.Web.CommonAPI do end end + def create_group(user, params) do + with {:ok, group_data, _} <- Builder.create_group(user, params), + {:ok, group, _} <- Pipeline.common_pipeline(group_data, local: true) do + {:ok, group} + end + end + def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]), :ok <- validate_chat_content_length(content, !!maybe_attachment), diff --git a/priv/repo/migrations/20210113150220_create_groups.exs b/priv/repo/migrations/20210113150220_create_groups.exs index 642326d51..e98be498e 100644 --- a/priv/repo/migrations/20210113150220_create_groups.exs +++ b/priv/repo/migrations/20210113150220_create_groups.exs @@ -6,7 +6,6 @@ defmodule Pleroma.Repo.Migrations.CreateGroups do add(:id, :uuid, primary_key: true) add(:user_id, references(:users, type: :uuid, on_delete: :delete_all), null: false) add(:owner_id, references(:users, type: :uuid, on_delete: :nilify_all)) - add(:members, {:array, :string}, default: []) add(:name, :text) add(:description, :text) add(:members_collection, :text) diff --git a/test/pleroma/group_test.exs b/test/pleroma/group_test.exs index e4c6cfd92..c10dd10f4 100644 --- a/test/pleroma/group_test.exs +++ b/test/pleroma/group_test.exs @@ -10,6 +10,32 @@ defmodule Pleroma.GroupTest do import Pleroma.Factory + test "get_for_object/1 gets a group based on the group object or the create activity" do + user = insert(:user) + + {:ok, group} = Group.create(%{owner_id: user.id, name: "cofe", description: "corndog"}) + group = Repo.preload(group, :user) + + group_object = %{ + "id" => group.user.ap_id, + "type" => "Group" + } + + assert group.id == Group.get_for_object(group_object).id + + # Same works if wrapped in a 'create' + group_create = %{ + "type" => "Create", + "object" => group_object + } + + assert group.id == Group.get_for_object(group_create).id + + # Nil for nonsense + + assert nil == Group.get_for_object(%{"nothing" => "PS4 games"}) + end + test "a user can create a group" do user = insert(:user) {:ok, group} = Group.create(%{owner_id: user.id, name: "cofe", description: "corndog"}) diff --git a/test/pleroma/web/activity_pub/object_validators/group_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/group_validation_test.exs new file mode 100644 index 000000000..3aab7493d --- /dev/null +++ b/test/pleroma/web/activity_pub/object_validators/group_validation_test.exs @@ -0,0 +1,24 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.GroupValidationTest do + use Pleroma.DataCase, async: true + + import Pleroma.Factory + + alias Pleroma.Web.ActivityPub.Builder + alias Pleroma.Web.ActivityPub.ObjectValidator + + describe "Group objects" do + test "it validates a group" do + user = insert(:user) + + {:ok, group_data, []} = Builder.group(user, "a group", "a description") + + {:ok, group, _} = ObjectValidator.validate(group_data, []) + + assert group + end + end +end diff --git a/test/pleroma/web/common_api/group_messaging_test.exs b/test/pleroma/web/common_api/group_messaging_test.exs new file mode 100644 index 000000000..2509428ae --- /dev/null +++ b/test/pleroma/web/common_api/group_messaging_test.exs @@ -0,0 +1,25 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.CommonAPI.GroupMessagingTest do + use Pleroma.DataCase + + import Pleroma.Factory + + alias Pleroma.Group + alias Pleroma.Web.CommonAPI + + describe "Group chats" do + test "local chat" do + user = insert(:user) + + {:ok, group_creation_activity} = + CommonAPI.create_group(user, %{name: "cofe", description: "for cofe enthusiasts"}) + + group = Group.get_for_object(group_creation_activity) + + assert group + end + end +end