diff --git a/config/config.exs b/config/config.exs index 776c5eef28..80ea57ddd2 100644 --- a/config/config.exs +++ b/config/config.exs @@ -747,7 +747,8 @@ events_actions: {10_000, 15}, password_reset: {1_800_000, 5}, account_confirmation_resend: {8_640_000, 5}, - ap_routes: {60_000, 15} + ap_routes: {60_000, 15}, + bites: {10_000, 10} config :pleroma, Pleroma.Workers.PurgeExpiredActivity, enabled: true, min_lifetime: 600 diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index e341ab8df9..ab11315e85 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -115,7 +115,9 @@ defmodule Pleroma.Constants do "Announce", "Undo", "Flag", - "EmojiReact" + "EmojiReact", + "Bite", + "Join" ] ) diff --git a/lib/pleroma/web/activity_pub/object_validators/bite_validator.ex b/lib/pleroma/web/activity_pub/object_validators/bite_validator.ex index a2e0bac85e..51e58640e3 100644 --- a/lib/pleroma/web/activity_pub/object_validators/bite_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/bite_validator.ex @@ -38,7 +38,7 @@ defp validate_data(cng) do |> validate_required([:id, :type, :actor, :to, :target]) |> validate_inclusion(:type, ["Bite"]) |> validate_actor_presence() - |> validate_actor_presence(field_name: :target) + |> validate_object_or_user_presence(field_name: :target) end def cast_and_validate(data) do diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 4ecdb19e1b..8e7cd7a5cb 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -497,6 +497,32 @@ def handle_incoming( end end + def handle_incoming( + %{"type" => "Bite", "target" => target_id} = data, + _options + ) do + target_id = + cond do + %User{ap_id: actor_id} = User.get_by_ap_id(target_id) -> + actor_id + + %Object{data: data} = Object.get_by_ap_id(target_id) -> + data["actor"] || data["attributedTo"] + + true -> + target_id + end + + with data = Map.put(data, "target", target_id), + :ok <- ObjectValidator.fetch_actor_and_object(data), + {:ok, activity, _meta} <- + Pipeline.common_pipeline(data, local: false) do + {:ok, activity} + else + e -> {:error, e} + end + end + def handle_incoming(%{"type" => type} = data, _options) when type in ~w{Like EmojiReact Announce Add Remove} do with :ok <- ObjectValidator.fetch_actor_and_object(data), diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 6b2983d9d8..19b1438717 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -1070,6 +1070,7 @@ def fetch_latest_bite( |> maybe_exclude_activity_id(exclude_activity) |> Activity.Queries.by_object_id(bitten_ap_id) |> order_by([activity], fragment("? desc nulls last", activity.id)) + |> exclude_rejected() |> limit(1) |> Repo.one() end @@ -1080,4 +1081,14 @@ defp maybe_exclude_activity_id(query, %Activity{id: activity_id}) do query |> where([a], a.id != ^activity_id) end + + defp exclude_rejected(query) do + rejected_activities = + "Reject" + |> Activity.Queries.by_type() + |> select([a], fragment("?->>'object'", a.data)) + + query + |> where([a], fragment("?->>'id'", a.data) not in subquery(rejected_activities)) + end end diff --git a/lib/pleroma/web/mastodon_api/controllers/bite_controller.ex b/lib/pleroma/web/mastodon_api/controllers/bite_controller.ex index 69d865cb9b..b9b1310103 100644 --- a/lib/pleroma/web/mastodon_api/controllers/bite_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/bite_controller.ex @@ -9,14 +9,13 @@ defmodule Pleroma.Web.MastodonAPI.BiteController do alias Pleroma.Web.CommonAPI alias Pleroma.Web.Plugs.OAuthScopesPlug - # alias Pleroma.Web.Plugs.RateLimiter + alias Pleroma.Web.Plugs.RateLimiter plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) - plug(OAuthScopesPlug, %{scopes: ["write:bite"]} when action == :bite) + plug(OAuthScopesPlug, %{scopes: ["write:bites"]} when action == :bite) - # plug(RateLimiter, [name: :relations_actions] when action in @relationship_actions) - # plug(RateLimiter, [name: :app_account_creation] when action == :create) + plug(RateLimiter, name: :bites) plug(:assign_account_by_id) diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld index 921c373af1..5a9e5c4bdc 100644 --- a/priv/static/schemas/litepub-0.1.jsonld +++ b/priv/static/schemas/litepub-0.1.jsonld @@ -44,6 +44,7 @@ "formerRepresentations": "litepub:formerRepresentations", "sm": "http://smithereen.software/ns#", "nonAnonymous": "sm:nonAnonymous", +<<<<<<< HEAD "votersCount": "toot:votersCount", "mz": "https://joinmobilizon.org/ns#", "joinMode": { @@ -67,6 +68,8 @@ "@id": "schema:location", "@type": "schema:Place" }, +======= +>>>>>>> bites-pleroma "Bite": "https://ns.mia.jetzt/as#Bite" } ] diff --git a/test/pleroma/web/activity_pub/object_validators/bite_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/bite_validator_test.exs new file mode 100644 index 0000000000..94e433d5af --- /dev/null +++ b/test/pleroma/web/activity_pub/object_validators/bite_validator_test.exs @@ -0,0 +1,51 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.BiteValidationTest do + use Pleroma.DataCase, async: true + + alias Pleroma.Web.ActivityPub.ObjectValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.BiteValidator + alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + describe "bites" do + setup do + biting = insert(:user) + bitten = insert(:user) + + valid_bite = %{ + "id" => Utils.generate_activity_id(), + "type" => "Bite", + "actor" => biting.ap_id, + "target" => bitten.ap_id, + "to" => [bitten.ap_id] + } + + %{valid_bite: valid_bite, biting: biting, bitten: bitten} + end + + test "returns ok when called in the ObjectValidator", %{valid_bite: valid_bite} do + {:ok, object, _meta} = ObjectValidator.validate(valid_bite, []) + + assert "id" in Map.keys(object) + end + + test "is valid for a valid object", %{valid_bite: valid_bite} do + assert BiteValidator.cast_and_validate(valid_bite).valid? + end + + test "is valid when biting an object", %{valid_bite: valid_bite, bitten: bitten} do + {:ok, activity} = CommonAPI.post(bitten, %{status: "uguu"}) + + valid_bite = + valid_bite + |> Map.put("target", activity.data["object"]) + + assert BiteValidator.cast_and_validate(valid_bite).valid? + end + end +end diff --git a/test/pleroma/web/mastodon_api/controllers/bite_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/bite_controller_test.exs new file mode 100644 index 0000000000..96cd38e762 --- /dev/null +++ b/test/pleroma/web/mastodon_api/controllers/bite_controller_test.exs @@ -0,0 +1,30 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.BiteControllerTest do + use Pleroma.Web.ConnCase + import Pleroma.Factory + + setup do: oauth_access(["write:bites"]) + + test "bites a user", %{conn: conn} do + %{id: bitten_id} = insert(:user) + + response = + conn + |> post("/api/v1/bite?id=#{bitten_id}") + |> json_response_and_validate_schema(200) + + assert response == %{} + end + + test "self harm is not supported", %{conn: conn, user: %{id: self_id}} do + response = + conn + |> post("/api/v1/bite?id=#{self_id}") + |> json_response_and_validate_schema(400) + + assert %{"error" => "Can not bite yourself"} = response + end +end