Merge branch 'feature/keyword-policy' into 'develop'
Add keyword policy See merge request pleroma/pleroma!794
This commit is contained in:
commit
1eecbc1cd1
@ -238,6 +238,11 @@ config :pleroma, :mrf_simple,
|
|||||||
reject: [],
|
reject: [],
|
||||||
accept: []
|
accept: []
|
||||||
|
|
||||||
|
config :pleroma, :mrf_keyword,
|
||||||
|
reject: [],
|
||||||
|
federated_timeline_removal: [],
|
||||||
|
replace: []
|
||||||
|
|
||||||
config :pleroma, :rich_media, enabled: true
|
config :pleroma, :rich_media, enabled: true
|
||||||
|
|
||||||
config :pleroma, :media_proxy,
|
config :pleroma, :media_proxy,
|
||||||
|
@ -171,6 +171,11 @@ This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:i
|
|||||||
* `delist_threshold`: Number of mentioned users after which the message gets delisted (the message can still be seen, but it will not show up in public timelines and mentioned users won't get notifications about it). Set to 0 to disable.
|
* `delist_threshold`: Number of mentioned users after which the message gets delisted (the message can still be seen, but it will not show up in public timelines and mentioned users won't get notifications about it). Set to 0 to disable.
|
||||||
* `reject_threshold`: Number of mentioned users after which the messaged gets rejected. Set to 0 to disable.
|
* `reject_threshold`: Number of mentioned users after which the messaged gets rejected. Set to 0 to disable.
|
||||||
|
|
||||||
|
## :mrf_keyword
|
||||||
|
* `reject`: A list of patterns which result in message being rejected, each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html)
|
||||||
|
* `federated_timeline_removal`: A list of patterns which result in message being removed from federated timelines (a.k.a unlisted), each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html)
|
||||||
|
* `replace`: A list of tuples containing `{pattern, replacement}`, `pattern` can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html)
|
||||||
|
|
||||||
## :media_proxy
|
## :media_proxy
|
||||||
* `enabled`: Enables proxying of remote media to the instance’s proxy
|
* `enabled`: Enables proxying of remote media to the instance’s proxy
|
||||||
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.
|
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.
|
||||||
|
73
lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
Normal file
73
lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# 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.KeywordPolicy do
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
defp string_matches?(string, pattern) when is_binary(pattern) do
|
||||||
|
String.contains?(string, pattern)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp string_matches?(string, pattern) do
|
||||||
|
String.match?(string, pattern)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_reject(%{"object" => %{"content" => content}} = message) do
|
||||||
|
if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern ->
|
||||||
|
string_matches?(content, pattern)
|
||||||
|
end) do
|
||||||
|
{:reject, nil}
|
||||||
|
else
|
||||||
|
{:ok, message}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_ftl_removal(%{"to" => to, "object" => %{"content" => content}} = message) do
|
||||||
|
if "https://www.w3.org/ns/activitystreams#Public" in to and
|
||||||
|
Enum.any?(Pleroma.Config.get([:mrf_keyword, :federated_timeline_removal]), fn pattern ->
|
||||||
|
string_matches?(content, pattern)
|
||||||
|
end) do
|
||||||
|
to = List.delete(to, "https://www.w3.org/ns/activitystreams#Public")
|
||||||
|
cc = ["https://www.w3.org/ns/activitystreams#Public" | message["cc"] || []]
|
||||||
|
|
||||||
|
message =
|
||||||
|
message
|
||||||
|
|> Map.put("to", to)
|
||||||
|
|> Map.put("cc", cc)
|
||||||
|
|
||||||
|
{:ok, message}
|
||||||
|
else
|
||||||
|
{:ok, message}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_replace(%{"object" => %{"content" => content}} = message) do
|
||||||
|
content =
|
||||||
|
Enum.reduce(Pleroma.Config.get([:mrf_keyword, :replace]), content, fn {pattern, replacement},
|
||||||
|
acc ->
|
||||||
|
String.replace(acc, pattern, replacement)
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, put_in(message["object"]["content"], content)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(%{"object" => %{"content" => nil}} = message) do
|
||||||
|
{:ok, message}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(%{"type" => "Create", "object" => %{"content" => _content}} = message) do
|
||||||
|
with {:ok, message} <- check_reject(message),
|
||||||
|
{:ok, message} <- check_ftl_removal(message),
|
||||||
|
{:ok, message} <- check_replace(message) do
|
||||||
|
{:ok, message}
|
||||||
|
else
|
||||||
|
_e ->
|
||||||
|
{:reject, nil}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(message), do: {:ok, message}
|
||||||
|
end
|
@ -44,6 +44,33 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||||||
Application.get_env(:pleroma, :mrf_simple)
|
Application.get_env(:pleroma, :mrf_simple)
|
||||||
|> Enum.into(%{})
|
|> Enum.into(%{})
|
||||||
|
|
||||||
|
# This horror is needed to convert regex sigils to strings
|
||||||
|
mrf_keyword =
|
||||||
|
Application.get_env(:pleroma, :mrf_keyword, [])
|
||||||
|
|> Enum.map(fn {key, value} ->
|
||||||
|
{key,
|
||||||
|
Enum.map(value, fn
|
||||||
|
{pattern, replacement} ->
|
||||||
|
%{
|
||||||
|
"pattern" =>
|
||||||
|
if not is_binary(pattern) do
|
||||||
|
inspect(pattern)
|
||||||
|
else
|
||||||
|
pattern
|
||||||
|
end,
|
||||||
|
"replacement" => replacement
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern ->
|
||||||
|
if not is_binary(pattern) do
|
||||||
|
inspect(pattern)
|
||||||
|
else
|
||||||
|
pattern
|
||||||
|
end
|
||||||
|
end)}
|
||||||
|
end)
|
||||||
|
|> Enum.into(%{})
|
||||||
|
|
||||||
mrf_policies =
|
mrf_policies =
|
||||||
MRF.get_policies()
|
MRF.get_policies()
|
||||||
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
|
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
|
||||||
@ -73,6 +100,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||||||
%{
|
%{
|
||||||
mrf_policies: mrf_policies,
|
mrf_policies: mrf_policies,
|
||||||
mrf_simple: mrf_simple,
|
mrf_simple: mrf_simple,
|
||||||
|
mrf_keyword: mrf_keyword,
|
||||||
mrf_user_allowlist: mrf_user_allowlist,
|
mrf_user_allowlist: mrf_user_allowlist,
|
||||||
quarantined_instances: quarantined
|
quarantined_instances: quarantined
|
||||||
}
|
}
|
||||||
|
111
test/web/activity_pub/mrf/keyword_policy_test.exs
Normal file
111
test/web/activity_pub/mrf/keyword_policy_test.exs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
# 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.KeywordPolicyTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.MRF.KeywordPolicy
|
||||||
|
|
||||||
|
setup do
|
||||||
|
Pleroma.Config.put([:mrf_keyword], %{reject: [], federated_timeline_removal: [], replace: []})
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "rejecting based on keywords" do
|
||||||
|
test "rejects if string matches" do
|
||||||
|
Pleroma.Config.put([:mrf_keyword, :reject], ["pun"])
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{"content" => "just a daily reminder that compLAINer is a good pun"}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:reject, nil} == KeywordPolicy.filter(message)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "rejects if regex matches" do
|
||||||
|
Pleroma.Config.put([:mrf_keyword, :reject], [~r/comp[lL][aA][iI][nN]er/])
|
||||||
|
|
||||||
|
assert true ==
|
||||||
|
Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content ->
|
||||||
|
message = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{
|
||||||
|
"content" => "just a daily reminder that #{content} is a good pun"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:reject, nil} == KeywordPolicy.filter(message)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "delisting from ftl based on keywords" do
|
||||||
|
test "delists if string matches" do
|
||||||
|
Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], ["pun"])
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{"content" => "just a daily reminder that compLAINer is a good pun"}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, result} = KeywordPolicy.filter(message)
|
||||||
|
assert ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"]
|
||||||
|
refute ["https://www.w3.org/ns/activitystreams#Public"] == result["to"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "delists if regex matches" do
|
||||||
|
Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], [~r/comp[lL][aA][iI][nN]er/])
|
||||||
|
|
||||||
|
assert true ==
|
||||||
|
Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content ->
|
||||||
|
message = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"object" => %{
|
||||||
|
"content" => "just a daily reminder that #{content} is a good pun"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, result} = KeywordPolicy.filter(message)
|
||||||
|
|
||||||
|
["https://www.w3.org/ns/activitystreams#Public"] == result["cc"] and
|
||||||
|
not (["https://www.w3.org/ns/activitystreams#Public"] == result["to"])
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "replacing keywords" do
|
||||||
|
test "replaces keyword if string matches" do
|
||||||
|
Pleroma.Config.put([:mrf_keyword, :replace], [{"opensource", "free software"}])
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"object" => %{"content" => "ZFS is opensource"}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, %{"object" => %{"content" => result}}} = KeywordPolicy.filter(message)
|
||||||
|
assert result == "ZFS is free software"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "replaces keyword if regex matches" do
|
||||||
|
Pleroma.Config.put([:mrf_keyword, :replace], [
|
||||||
|
{~r/open(-|\s)?source\s?(software)?/, "free software"}
|
||||||
|
])
|
||||||
|
|
||||||
|
assert true ==
|
||||||
|
Enum.all?(["opensource", "open-source", "open source"], fn content ->
|
||||||
|
message = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"object" => %{"content" => "ZFS is #{content}"}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, %{"object" => %{"content" => result}}} = KeywordPolicy.filter(message)
|
||||||
|
result == "ZFS is free software"
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user