Restrict statuses that contain user's irreversible filters

This commit is contained in:
Sergey Suprunenko 2019-11-16 22:54:13 +01:00 committed by Alexander Strizhakov
parent c2a052a346
commit 4a8c26654e
No known key found for this signature in database
GPG key ID: 022896A53AEF1381
6 changed files with 141 additions and 4 deletions

View file

@ -34,10 +34,18 @@ def get(id, %{id: user_id} = _user) do
Repo.one(query)
end
def get_filters(%User{id: user_id} = _user) do
def get_active(query) do
from(f in query, where: is_nil(f.expires_at) or f.expires_at > ^NaiveDateTime.utc_now())
end
def get_irreversible(query) do
from(f in query, where: f.hide)
end
def get_by_user(query, %User{id: user_id} = _user) do
query =
from(
f in Pleroma.Filter,
f in query,
where: f.user_id == ^user_id,
order_by: [desc: :id]
)
@ -95,4 +103,34 @@ def update(%Pleroma.Filter{} = filter, params) do
|> validate_required([:phrase, :context])
|> Repo.update()
end
def compose_regex(user_or_filters, format \\ :postgres)
def compose_regex(%User{} = user, format) do
__MODULE__
|> get_active()
|> get_irreversible()
|> get_by_user(user)
|> compose_regex(format)
end
def compose_regex([_ | _] = filters, format) do
phrases =
filters
|> Enum.map(& &1.phrase)
|> Enum.join("|")
case format do
:postgres ->
"\\y(#{phrases})\\y"
:re ->
~r/\b#{phrases}\b/i
_ ->
nil
end
end
def compose_regex(_, _), do: nil
end

View file

@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Constants
alias Pleroma.Conversation
alias Pleroma.Conversation.Participation
alias Pleroma.Filter
alias Pleroma.Maps
alias Pleroma.Notification
alias Pleroma.Object
@ -961,6 +962,26 @@ defp restrict_instance(query, %{instance: instance}) do
defp restrict_instance(query, _), do: query
defp restrict_filtered(query, %{user: %User{} = user}) do
case Filter.compose_regex(user) do
nil ->
query
regex ->
from([activity, object] in query,
where:
fragment("not(?->>'content' ~* ?)", object.data, ^regex) or
activity.actor == ^user.ap_id
)
end
end
defp restrict_filtered(query, %{blocking_user: %User{} = user}) do
restrict_filtered(query, %{user: user})
end
defp restrict_filtered(query, _), do: query
defp exclude_poll_votes(query, %{include_poll_votes: true}), do: query
defp exclude_poll_votes(query, _) do
@ -1099,6 +1120,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|> restrict_muted_reblogs(restrict_muted_reblogs_opts)
|> restrict_instance(opts)
|> restrict_announce_object_actor(opts)
|> restrict_filtered(opts)
|> Activity.restrict_deactivated_users()
|> exclude_poll_votes(opts)
|> exclude_chat_messages(opts)

View file

@ -22,7 +22,7 @@ defmodule Pleroma.Web.MastodonAPI.FilterController do
@doc "GET /api/v1/filters"
def index(%{assigns: %{user: user}} = conn, _) do
filters = Filter.get_filters(user)
filters = Filter.get_by_user(Filter, user)
render(conn, "index.json", filters: filters)
end

View file

@ -126,7 +126,7 @@ test "getting all filters by an user" do
{:ok, filter_one} = Pleroma.Filter.create(query_one)
{:ok, filter_two} = Pleroma.Filter.create(query_two)
filters = Pleroma.Filter.get_filters(user)
filters = Pleroma.Filter.get_by_user(Pleroma.Filter, user)
assert filter_one in filters
assert filter_two in filters
end

View file

@ -428,4 +428,12 @@ def mfa_token_factory do
user: build(:user)
}
end
def filter_factory do
%Pleroma.Filter{
user: build(:user),
filter_id: sequence(:filter_id, & &1),
phrase: "cofe"
}
end
end

View file

@ -785,6 +785,75 @@ test "excludes reblogs on request" do
assert activity == expected_activity
end
describe "irreversible filters" do
setup do
user = insert(:user)
user_two = insert(:user)
insert(:filter, user: user_two, phrase: "cofe", hide: true)
insert(:filter, user: user_two, phrase: "ok boomer", hide: true)
insert(:filter, user: user_two, phrase: "test", hide: false)
params = %{
"type" => ["Create", "Announce"],
"user" => user_two
}
{:ok, %{user: user, user_two: user_two, params: params}}
end
test "it returns statuses if they don't contain exact filter words", %{
user: user,
params: params
} do
{:ok, _} = CommonAPI.post(user, %{"status" => "hey"})
{:ok, _} = CommonAPI.post(user, %{"status" => "got cofefe?"})
{:ok, _} = CommonAPI.post(user, %{"status" => "I am not a boomer"})
{:ok, _} = CommonAPI.post(user, %{"status" => "ok boomers"})
{:ok, _} = CommonAPI.post(user, %{"status" => "ccofee is not a word"})
{:ok, _} = CommonAPI.post(user, %{"status" => "this is a test"})
activities = ActivityPub.fetch_activities([], params)
assert Enum.count(activities) == 6
end
test "it does not filter user's own statuses", %{user_two: user_two, params: params} do
{:ok, _} = CommonAPI.post(user_two, %{"status" => "Give me some cofe!"})
{:ok, _} = CommonAPI.post(user_two, %{"status" => "ok boomer"})
activities = ActivityPub.fetch_activities([], params)
assert Enum.count(activities) == 2
end
test "it excludes statuses with filter words", %{user: user, params: params} do
{:ok, _} = CommonAPI.post(user, %{"status" => "Give me some cofe!"})
{:ok, _} = CommonAPI.post(user, %{"status" => "ok boomer"})
{:ok, _} = CommonAPI.post(user, %{"status" => "is it a cOfE?"})
{:ok, _} = CommonAPI.post(user, %{"status" => "cofe is all I need"})
{:ok, _} = CommonAPI.post(user, %{"status" => "— ok BOOMER\n"})
activities = ActivityPub.fetch_activities([], params)
assert Enum.empty?(activities)
end
test "it returns all statuses if user does not have any filters" do
another_user = insert(:user)
{:ok, _} = CommonAPI.post(another_user, %{"status" => "got cofe?"})
{:ok, _} = CommonAPI.post(another_user, %{"status" => "test!"})
activities =
ActivityPub.fetch_activities([], %{
"type" => ["Create", "Announce"],
"user" => another_user
})
assert Enum.count(activities) == 2
end
end
describe "public fetch activities" do
test "doesn't retrieve unlisted activities" do
user = insert(:user)