Merge branch 'block-behavior' into 'develop'
Configurable block visibility, fixes #2123 Closes #2123 See merge request pleroma/pleroma!3242
This commit is contained in:
commit
e2772d6bf1
|
@ -135,6 +135,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Support pagination of blocks and mutes.
|
- Support pagination of blocks and mutes.
|
||||||
- Account backup.
|
- Account backup.
|
||||||
- Configuration: Add `:instance, autofollowing_nicknames` setting to provide a way to make accounts automatically follow new users that register on the local Pleroma instance.
|
- Configuration: Add `:instance, autofollowing_nicknames` setting to provide a way to make accounts automatically follow new users that register on the local Pleroma instance.
|
||||||
|
- `[:activitypub, :blockers_visible]` config to control visibility of blockers.
|
||||||
- Ability to view remote timelines, with ex. `/api/v1/timelines/public?instance=lain.com` and streams `public:remote` and `public:remote:media`.
|
- Ability to view remote timelines, with ex. `/api/v1/timelines/public?instance=lain.com` and streams `public:remote` and `public:remote:media`.
|
||||||
- The site title is now injected as a `title` tag like preloads or metadata.
|
- The site title is now injected as a `title` tag like preloads or metadata.
|
||||||
- Password reset tokens now are not accepted after a certain age.
|
- Password reset tokens now are not accepted after a certain age.
|
||||||
|
|
|
@ -349,6 +349,7 @@
|
||||||
config :pleroma, :activitypub,
|
config :pleroma, :activitypub,
|
||||||
unfollow_blocked: true,
|
unfollow_blocked: true,
|
||||||
outgoing_blocks: true,
|
outgoing_blocks: true,
|
||||||
|
blockers_visible: true,
|
||||||
follow_handshake_timeout: 500,
|
follow_handshake_timeout: 500,
|
||||||
note_replies_output_limit: 5,
|
note_replies_output_limit: 5,
|
||||||
sign_object_fetches: true,
|
sign_object_fetches: true,
|
||||||
|
|
|
@ -1670,6 +1670,11 @@
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Whether to federate blocks to other instances"
|
description: "Whether to federate blocks to other instances"
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
key: :blockers_visible,
|
||||||
|
type: :boolean,
|
||||||
|
description: "Whether a user can see someone who has blocked them"
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
key: :sign_object_fetches,
|
key: :sign_object_fetches,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
|
|
|
@ -230,6 +230,7 @@ Notes:
|
||||||
### :activitypub
|
### :activitypub
|
||||||
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
|
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
|
||||||
* `outgoing_blocks`: Whether to federate blocks to other instances
|
* `outgoing_blocks`: Whether to federate blocks to other instances
|
||||||
|
* `blockers_visible`: Whether a user can see the posts of users who blocked them
|
||||||
* `deny_follow_blocked`: Whether to disallow following an account that has blocked the user in question
|
* `deny_follow_blocked`: Whether to disallow following an account that has blocked the user in question
|
||||||
* `sign_object_fetches`: Sign object fetches with HTTP signatures
|
* `sign_object_fetches`: Sign object fetches with HTTP signatures
|
||||||
* `authorized_fetch_mode`: Require HTTP signatures for AP fetches
|
* `authorized_fetch_mode`: Require HTTP signatures for AP fetches
|
||||||
|
|
|
@ -128,6 +128,7 @@ def for_user_query(user, opts \\ %{}) do
|
||||||
|> where([user_actor: user_actor], user_actor.is_active)
|
|> where([user_actor: user_actor], user_actor.is_active)
|
||||||
|> exclude_notification_muted(user, exclude_notification_muted_opts)
|
|> exclude_notification_muted(user, exclude_notification_muted_opts)
|
||||||
|> exclude_blocked(user, exclude_blocked_opts)
|
|> exclude_blocked(user, exclude_blocked_opts)
|
||||||
|
|> exclude_blockers(user)
|
||||||
|> exclude_filtered(user)
|
|> exclude_filtered(user)
|
||||||
|> exclude_visibility(opts)
|
|> exclude_visibility(opts)
|
||||||
end
|
end
|
||||||
|
@ -141,6 +142,17 @@ defp exclude_blocked(query, user, opts) do
|
||||||
|> FollowingRelationship.keep_following_or_not_domain_blocked(user)
|
|> FollowingRelationship.keep_following_or_not_domain_blocked(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp exclude_blockers(query, user) do
|
||||||
|
if Pleroma.Config.get([:activitypub, :blockers_visible]) == true do
|
||||||
|
query
|
||||||
|
else
|
||||||
|
blocker_ap_ids = User.incoming_relationships_ungrouped_ap_ids(user, [:block])
|
||||||
|
|
||||||
|
query
|
||||||
|
|> where([n, a], a.actor not in ^blocker_ap_ids)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp exclude_notification_muted(query, _, %{@include_muted_option => true}) do
|
defp exclude_notification_muted(query, _, %{@include_muted_option => true}) do
|
||||||
query
|
query
|
||||||
end
|
end
|
||||||
|
|
|
@ -441,6 +441,7 @@ def fetch_activities_for_context_query(context, opts) do
|
||||||
|> maybe_preload_bookmarks(opts)
|
|> maybe_preload_bookmarks(opts)
|
||||||
|> maybe_set_thread_muted_field(opts)
|
|> maybe_set_thread_muted_field(opts)
|
||||||
|> restrict_blocked(opts)
|
|> restrict_blocked(opts)
|
||||||
|
|> restrict_blockers_visibility(opts)
|
||||||
|> restrict_recipients(recipients, opts[:user])
|
|> restrict_recipients(recipients, opts[:user])
|
||||||
|> restrict_filtered(opts)
|
|> restrict_filtered(opts)
|
||||||
|> where(
|
|> where(
|
||||||
|
@ -1028,7 +1029,10 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
|
||||||
|
|
||||||
from(
|
from(
|
||||||
[activity, object: o] in query,
|
[activity, object: o] in query,
|
||||||
|
# You don't block the author
|
||||||
where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids),
|
where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids),
|
||||||
|
|
||||||
|
# You don't block any recipients, and didn't author the post
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
"((not (? && ?)) or ? = ?)",
|
"((not (? && ?)) or ? = ?)",
|
||||||
|
@ -1037,12 +1041,18 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
|
||||||
activity.actor,
|
activity.actor,
|
||||||
^user.ap_id
|
^user.ap_id
|
||||||
),
|
),
|
||||||
|
|
||||||
|
# You don't block the domain of any recipients, and didn't author the post
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
"recipients_contain_blocked_domains(?, ?) = false",
|
"(recipients_contain_blocked_domains(?, ?) = false) or ? = ?",
|
||||||
activity.recipients,
|
activity.recipients,
|
||||||
^domain_blocks
|
^domain_blocks,
|
||||||
|
activity.actor,
|
||||||
|
^user.ap_id
|
||||||
),
|
),
|
||||||
|
|
||||||
|
# It's not a boost of a user you block
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
|
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
|
||||||
|
@ -1050,6 +1060,8 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
|
||||||
activity.data,
|
activity.data,
|
||||||
^blocked_ap_ids
|
^blocked_ap_ids
|
||||||
),
|
),
|
||||||
|
|
||||||
|
# You don't block the author's domain, and also don't follow the author
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
"(not (split_part(?, '/', 3) = ANY(?))) or ? = ANY(?)",
|
"(not (split_part(?, '/', 3) = ANY(?))) or ? = ANY(?)",
|
||||||
|
@ -1058,6 +1070,8 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
|
||||||
activity.actor,
|
activity.actor,
|
||||||
^following_ap_ids
|
^following_ap_ids
|
||||||
),
|
),
|
||||||
|
|
||||||
|
# Same as above, but checks the Object
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
"(not (split_part(?->>'actor', '/', 3) = ANY(?))) or (?->>'actor') = ANY(?)",
|
"(not (split_part(?->>'actor', '/', 3) = ANY(?))) or (?->>'actor') = ANY(?)",
|
||||||
|
@ -1071,6 +1085,31 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
|
||||||
|
|
||||||
defp restrict_blocked(query, _), do: query
|
defp restrict_blocked(query, _), do: query
|
||||||
|
|
||||||
|
defp restrict_blockers_visibility(query, %{blocking_user: %User{} = user}) do
|
||||||
|
if Config.get([:activitypub, :blockers_visible]) == true do
|
||||||
|
query
|
||||||
|
else
|
||||||
|
blocker_ap_ids = User.incoming_relationships_ungrouped_ap_ids(user, [:block])
|
||||||
|
|
||||||
|
from(
|
||||||
|
activity in query,
|
||||||
|
# The author doesn't block you
|
||||||
|
where: fragment("not (? = ANY(?))", activity.actor, ^blocker_ap_ids),
|
||||||
|
|
||||||
|
# It's not a boost of a user that blocks you
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
|
||||||
|
activity.data,
|
||||||
|
activity.data,
|
||||||
|
^blocker_ap_ids
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_blockers_visibility(query, _), do: query
|
||||||
|
|
||||||
defp restrict_unlisted(query, %{restrict_unlisted: true}) do
|
defp restrict_unlisted(query, %{restrict_unlisted: true}) do
|
||||||
from(
|
from(
|
||||||
activity in query,
|
activity in query,
|
||||||
|
@ -1297,6 +1336,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
||||||
|> restrict_state(opts)
|
|> restrict_state(opts)
|
||||||
|> restrict_favorited_by(opts)
|
|> restrict_favorited_by(opts)
|
||||||
|> restrict_blocked(restrict_blocked_opts)
|
|> restrict_blocked(restrict_blocked_opts)
|
||||||
|
|> restrict_blockers_visibility(opts)
|
||||||
|> restrict_muted(restrict_muted_opts)
|
|> restrict_muted(restrict_muted_opts)
|
||||||
|> restrict_filtered(opts)
|
|> restrict_filtered(opts)
|
||||||
|> restrict_media(opts)
|
|> restrict_media(opts)
|
||||||
|
|
|
@ -776,6 +776,18 @@ test "doesn't return blocked activities" do
|
||||||
assert Enum.member?(activities, activity_one)
|
assert Enum.member?(activities, activity_one)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "always see your own posts even when they address people you block" do
|
||||||
|
user = insert(:user)
|
||||||
|
blockee = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _} = User.block(user, blockee)
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "hey! @#{blockee.nickname}"})
|
||||||
|
|
||||||
|
activities = ActivityPub.fetch_activities([], %{blocking_user: user})
|
||||||
|
|
||||||
|
assert Enum.member?(activities, activity)
|
||||||
|
end
|
||||||
|
|
||||||
test "doesn't return transitive interactions concerning blocked users" do
|
test "doesn't return transitive interactions concerning blocked users" do
|
||||||
blocker = insert(:user)
|
blocker = insert(:user)
|
||||||
blockee = insert(:user)
|
blockee = insert(:user)
|
||||||
|
@ -875,6 +887,21 @@ test "doesn't return activities from blocked domains" do
|
||||||
refute repeat_activity in activities
|
refute repeat_activity in activities
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "see your own posts even when they adress actors from blocked domains" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
domain = "dogwhistle.zone"
|
||||||
|
domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
|
||||||
|
|
||||||
|
{:ok, user} = User.block_domain(user, domain)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "hey! @#{domain_user.nickname}"})
|
||||||
|
|
||||||
|
activities = ActivityPub.fetch_activities([], %{blocking_user: user})
|
||||||
|
|
||||||
|
assert Enum.member?(activities, activity)
|
||||||
|
end
|
||||||
|
|
||||||
test "does return activities from followed users on blocked domains" do
|
test "does return activities from followed users on blocked domains" do
|
||||||
domain = "meanies.social"
|
domain = "meanies.social"
|
||||||
domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
|
domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
|
||||||
|
|
|
@ -101,6 +101,25 @@ test "by default, does not contain pleroma:report" do
|
||||||
assert [_] = result
|
assert [_] = result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "excludes mentions from blockers when blockers_visible is false" do
|
||||||
|
clear_config([:activitypub, :blockers_visible], false)
|
||||||
|
|
||||||
|
%{user: user, conn: conn} = oauth_access(["read:notifications"])
|
||||||
|
blocker = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _} = CommonAPI.block(blocker, user)
|
||||||
|
{:ok, activity} = CommonAPI.post(blocker, %{status: "hi @#{user.nickname}"})
|
||||||
|
|
||||||
|
{:ok, [_notification]} = Notification.create_notifications(activity)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/notifications")
|
||||||
|
|
||||||
|
assert [] == json_response_and_validate_schema(conn, 200)
|
||||||
|
end
|
||||||
|
|
||||||
test "getting a single notification" do
|
test "getting a single notification" do
|
||||||
%{user: user, conn: conn} = oauth_access(["read:notifications"])
|
%{user: user, conn: conn} = oauth_access(["read:notifications"])
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
|
@ -273,6 +273,24 @@ test "doesn't return replies if follower is posting with blocked user" do
|
||||||
[%{"id" => ^reply_from_me}, %{"id" => ^activity_id}] = response
|
[%{"id" => ^reply_from_me}, %{"id" => ^activity_id}] = response
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "doesn't return posts from users who blocked you when :blockers_visible is disabled" do
|
||||||
|
clear_config([:activitypub, :blockers_visible], false)
|
||||||
|
|
||||||
|
%{conn: conn, user: blockee} = oauth_access(["read:statuses"])
|
||||||
|
blocker = insert(:user)
|
||||||
|
{:ok, _} = User.block(blocker, blockee)
|
||||||
|
|
||||||
|
conn = assign(conn, :user, blockee)
|
||||||
|
|
||||||
|
{:ok, _} = CommonAPI.post(blocker, %{status: "hey!"})
|
||||||
|
|
||||||
|
response =
|
||||||
|
get(conn, "/api/v1/timelines/public")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert length(response) == 0
|
||||||
|
end
|
||||||
|
|
||||||
test "doesn't return replies if follow is posting with users from blocked domain" do
|
test "doesn't return replies if follow is posting with users from blocked domain" do
|
||||||
%{conn: conn, user: blocker} = oauth_access(["read:statuses"])
|
%{conn: conn, user: blocker} = oauth_access(["read:statuses"])
|
||||||
friend = insert(:user)
|
friend = insert(:user)
|
||||||
|
|
Loading…
Reference in New Issue