logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma
commit: 13b6aefd59776b5979175cfce8d6d8dd0183a324
parent: 09b4f7269e5307f29fc8ca7b548e93cd93836dc5
Author: lain <lain@soykaf.club>
Date:   Fri, 15 Nov 2019 12:44:54 +0000

Merge branch 'feature/mrf-object-age-policy' into 'develop'

MRF: add ObjectAgePolicy

See merge request pleroma/pleroma!1982

Diffstat:

MCHANGELOG.md1+
Mconfig/config.exs4++++
Mdocs/configuration/cheatsheet.md8++++++++
Alib/pleroma/web/activity_pub/mrf/object_age_policy.ex101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/web/activity_pub/mrf/object_age_policy_test.exs105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 219 insertions(+), 0 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -42,6 +42,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Static Frontend: Add the ability to render user profiles and notices server-side without requiring JS app. - Mix task to re-count statuses for all users (`mix pleroma.count_statuses`) - Support for `X-Forwarded-For` and similar HTTP headers which used by reverse proxies to pass a real user IP address to the backend. Must not be enabled unless your instance is behind at least one reverse proxy (such as Nginx, Apache HTTPD or Varnish Cache). +- MRF: New module which handles incoming posts based on their age. By default, all incoming posts that are older than 2 days will be unlisted and not shown to their followers. <details> <summary>API Changes</summary> diff --git a/config/config.exs b/config/config.exs @@ -381,6 +381,10 @@ config :pleroma, :mrf_vocabulary, accept: [], reject: [] +config :pleroma, :mrf_object_age, + threshold: 172_800, + actions: [:delist, :strip_followers] + config :pleroma, :rich_media, enabled: true, ignore_hosts: [], diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md @@ -41,6 +41,7 @@ You shouldn't edit the base config directly to avoid breakages and merge conflic * `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed. * `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (See [`:mrf_mention`](#mrf_mention)). * `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (See [`:mrf_vocabulary`](#mrf_vocabulary)). + * `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)). * `public`: Makes the client API in authentificated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. * `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send. * `managed_config`: Whenether the config for pleroma-fe is configured in [:frontend_configurations](#frontend_configurations) or in ``static/config.json``. @@ -137,6 +138,13 @@ config :pleroma, :mrf_user_allowlist, "example.org": ["https://example.org/users/admin"] ``` +#### :mrf_object_age +* `threshold`: Required age (in seconds) of a post before actions are taken. +* `actions`: A list of actions to apply to the post: + * `:delist` removes the post from public timelines + * `:strip_followers` removes followers from the ActivityPub recipient list, ensuring they won't be delivered to home timelines + * `:reject` rejects the message entirely + ### :activitypub * ``unfollow_blocked``: Whether blocks result in people getting unfollowed * ``outgoing_blocks``: Whether to federate blocks to other instances diff --git a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex @@ -0,0 +1,101 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do + alias Pleroma.Config + alias Pleroma.User + alias Pleroma.Web.ActivityPub.MRF + + require Pleroma.Constants + + @moduledoc "Filter activities depending on their age" + @behaviour MRF + + defp check_date(%{"published" => published} = message) do + with %DateTime{} = now <- DateTime.utc_now(), + {:ok, %DateTime{} = then, _} <- DateTime.from_iso8601(published), + max_ttl <- Config.get([:mrf_object_age, :threshold]), + {:ttl, false} <- {:ttl, DateTime.diff(now, then) > max_ttl} do + {:ok, message} + else + {:ttl, true} -> + {:reject, nil} + + e -> + {:error, e} + end + end + + defp check_reject(message, actions) do + if :reject in actions do + {:reject, nil} + else + {:ok, message} + end + end + + defp check_delist(message, actions) do + if :delist in actions do + with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do + to = List.delete(message["to"], Pleroma.Constants.as_public()) ++ [user.follower_address] + cc = List.delete(message["cc"], user.follower_address) ++ [Pleroma.Constants.as_public()] + + message = + message + |> Map.put("to", to) + |> Map.put("cc", cc) + + {:ok, message} + else + # Unhandleable error: somebody is messing around, just drop the message. + _e -> + {:reject, nil} + end + else + {:ok, message} + end + end + + defp check_strip_followers(message, actions) do + if :strip_followers in actions do + with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do + to = List.delete(message["to"], user.follower_address) + cc = List.delete(message["cc"], user.follower_address) + + message = + message + |> Map.put("to", to) + |> Map.put("cc", cc) + + {:ok, message} + else + # Unhandleable error: somebody is messing around, just drop the message. + _e -> + {:reject, nil} + end + else + {:ok, message} + end + end + + @impl true + def filter(%{"type" => "Create", "published" => _} = message) do + with actions <- Config.get([:mrf_object_age, :actions]), + {:reject, _} <- check_date(message), + {:ok, message} <- check_reject(message, actions), + {:ok, message} <- check_delist(message, actions), + {:ok, message} <- check_strip_followers(message, actions) do + {:ok, message} + else + # check_date() is allowed to short-circuit the pipeline + e -> e + end + end + + @impl true + def filter(message), do: {:ok, message} + + @impl true + def describe, do: {:ok, %{}} +end diff --git a/test/web/activity_pub/mrf/object_age_policy_test.exs b/test/web/activity_pub/mrf/object_age_policy_test.exs @@ -0,0 +1,105 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicyTest do + use Pleroma.DataCase + alias Pleroma.Config + alias Pleroma.User + alias Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy + alias Pleroma.Web.ActivityPub.Visibility + + clear_config([:mrf_object_age]) do + Config.put(:mrf_object_age, + threshold: 172_800, + actions: [:delist, :strip_followers] + ) + end + + setup_all do + Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + + describe "with reject action" do + test "it rejects an old post" do + Config.put([:mrf_object_age, :actions], [:reject]) + + data = + File.read!("test/fixtures/mastodon-post-activity.json") + |> Poison.decode!() + + {:reject, _} = ObjectAgePolicy.filter(data) + end + + test "it allows a new post" do + Config.put([:mrf_object_age, :actions], [:reject]) + + data = + File.read!("test/fixtures/mastodon-post-activity.json") + |> Poison.decode!() + |> Map.put("published", DateTime.utc_now() |> DateTime.to_iso8601()) + + {:ok, _} = ObjectAgePolicy.filter(data) + end + end + + describe "with delist action" do + test "it delists an old post" do + Config.put([:mrf_object_age, :actions], [:delist]) + + data = + File.read!("test/fixtures/mastodon-post-activity.json") + |> Poison.decode!() + + {:ok, _u} = User.get_or_fetch_by_ap_id(data["actor"]) + + {:ok, data} = ObjectAgePolicy.filter(data) + + assert Visibility.get_visibility(%{data: data}) == "unlisted" + end + + test "it allows a new post" do + Config.put([:mrf_object_age, :actions], [:delist]) + + data = + File.read!("test/fixtures/mastodon-post-activity.json") + |> Poison.decode!() + |> Map.put("published", DateTime.utc_now() |> DateTime.to_iso8601()) + + {:ok, _user} = User.get_or_fetch_by_ap_id(data["actor"]) + + {:ok, ^data} = ObjectAgePolicy.filter(data) + end + end + + describe "with strip_followers action" do + test "it strips followers collections from an old post" do + Config.put([:mrf_object_age, :actions], [:strip_followers]) + + data = + File.read!("test/fixtures/mastodon-post-activity.json") + |> Poison.decode!() + + {:ok, user} = User.get_or_fetch_by_ap_id(data["actor"]) + + {:ok, data} = ObjectAgePolicy.filter(data) + + refute user.follower_address in data["to"] + refute user.follower_address in data["cc"] + end + + test "it allows a new post" do + Config.put([:mrf_object_age, :actions], [:strip_followers]) + + data = + File.read!("test/fixtures/mastodon-post-activity.json") + |> Poison.decode!() + |> Map.put("published", DateTime.utc_now() |> DateTime.to_iso8601()) + + {:ok, _u} = User.get_or_fetch_by_ap_id(data["actor"]) + + {:ok, ^data} = ObjectAgePolicy.filter(data) + end + end +end