Merge remote-tracking branch 'pleroma/develop' into feature/addressable-lists

This commit is contained in:
Egor Kislitsyn 2019-06-05 12:54:30 +07:00
commit 6ba9055b51
24 changed files with 332 additions and 49 deletions

View file

@ -51,6 +51,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- MRF: Support for stripping avatars and banner images from specific instances (`mrf_simple`)
- MRF: Support for running subchains.
- Addressable lists
- Configuration: `skip_thread_containment` option
### Changed
- **Breaking:** Configuration: move from Pleroma.Mailer to Pleroma.Emails.Mailer

View file

@ -243,7 +243,8 @@
max_report_comment_size: 1000,
safe_dm_mentions: false,
healthcheck: false,
remote_post_retention_days: 90
remote_post_retention_days: 90,
skip_thread_containment: false
config :pleroma, :app_account_creation, enabled: true, max_requests: 25, interval: 1800

View file

@ -83,6 +83,7 @@ Additional parameters can be added to the JSON body/Form data:
- `show_role` - if true, user's role (e.g admin, moderator) will be exposed to anyone in the API
- `default_scope` - the scope returned under `privacy` key in Source subentity
- `pleroma_settings_store` - Opaque user settings to be saved on the backend.
- `skip_thread_containment` - if true, skip filtering out broken threads
### Pleroma Settings Store
Pleroma has mechanism that allows frontends to save blobs of json for each user on the backend. This can be used to save frontend-specific settings for a user that the backend does not need to know about.

View file

@ -111,6 +111,7 @@ config :pleroma, Pleroma.Emails.Mailer,
* `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). (Default: `false`)
* `healthcheck`: if set to true, system data will be shown on ``/api/pleroma/healthcheck``.
* `remote_post_retention_days`: the default amount of days to retain remote posts when pruning the database
* `skip_thread_containment`: Skip filter out broken threads. the default is `false`.
## :app_account_creation
REST API for creating an account settings

View file

@ -79,5 +79,6 @@ def for_user_with_last_activity_id(user, params \\ %{}) do
| last_activity_id: activity_id
}
end)
|> Enum.filter(& &1.last_activity_id)
end
end

View file

@ -97,10 +97,22 @@ defp load do
# There was some other error
Logger.error("Could not access the custom emoji directory #{emoji_dir_path}: #{e}")
{:ok, packs} ->
{:ok, results} ->
grouped = Enum.group_by(results, &File.dir?/1)
packs = grouped[true] || []
files = grouped[false] || []
# Print the packs we've found
Logger.info("Found emoji packs: #{Enum.join(packs, ", ")}")
if not Enum.empty?(files) do
Logger.warn(
"Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{
Enum.join(files, ", ")
}"
)
end
emojis =
Enum.flat_map(
packs,

View file

@ -55,6 +55,8 @@ defmodule Pleroma.User.Info do
}
)
field(:skip_thread_containment, :boolean, default: false)
# Found in the wild
# ap_id -> Where is this used?
# bio -> Where is this used?
@ -220,6 +222,7 @@ def profile_update(info, params) do
:hide_favorites,
:background,
:show_role,
:skip_thread_containment,
:pleroma_settings_store
])
end

View file

@ -4,6 +4,7 @@
defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Conversation
alias Pleroma.Notification
alias Pleroma.Object
@ -72,7 +73,7 @@ defp check_actor_is_active(actor) do
end
defp check_remote_limit(%{"object" => %{"content" => content}}) when not is_nil(content) do
limit = Pleroma.Config.get([:instance, :remote_limit])
limit = Config.get([:instance, :remote_limit])
String.length(content) <= limit
end
@ -410,8 +411,8 @@ def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ tru
end
def block(blocker, blocked, activity_id \\ nil, local \\ true) do
outgoing_blocks = Pleroma.Config.get([:activitypub, :outgoing_blocks])
unfollow_blocked = Pleroma.Config.get([:activitypub, :unfollow_blocked])
outgoing_blocks = Config.get([:activitypub, :outgoing_blocks])
unfollow_blocked = Config.get([:activitypub, :unfollow_blocked])
if unfollow_blocked do
follow_activity = fetch_latest_follow(blocker, blocked)
@ -556,14 +557,11 @@ defp restrict_visibility(query, %{visibility: visibility})
defp restrict_visibility(query, %{visibility: visibility})
when visibility in @valid_visibilities do
query =
from(
a in query,
where:
fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
)
query
from(
a in query,
where:
fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
)
end
defp restrict_visibility(_query, %{visibility: visibility})
@ -573,17 +571,24 @@ defp restrict_visibility(_query, %{visibility: visibility})
defp restrict_visibility(query, _visibility), do: query
defp restrict_thread_visibility(query, %{"user" => %User{ap_id: ap_id}}) do
query =
from(
a in query,
where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data)
)
defp restrict_thread_visibility(query, _, %{skip_thread_containment: true} = _),
do: query
query
defp restrict_thread_visibility(
query,
%{"user" => %User{info: %{skip_thread_containment: true}}},
_
),
do: query
defp restrict_thread_visibility(query, %{"user" => %User{ap_id: ap_id}}, _) do
from(
a in query,
where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data)
)
end
defp restrict_thread_visibility(query, _), do: query
defp restrict_thread_visibility(query, _, _), do: query
def fetch_user_activities(user, reading_user, params \\ %{}) do
params =
@ -860,6 +865,10 @@ defp maybe_order(query, %{order: :asc}) do
defp maybe_order(query, _), do: query
def fetch_activities_query(recipients, opts \\ %{}) do
config = %{
skip_thread_containment: Config.get([:instance, :skip_thread_containment])
}
Activity
|> maybe_preload_objects(opts)
|> maybe_preload_bookmarks(opts)
@ -879,7 +888,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|> restrict_muted(opts)
|> restrict_media(opts)
|> restrict_visibility(opts)
|> restrict_thread_visibility(opts)
|> restrict_thread_visibility(opts, config)
|> restrict_replies(opts)
|> restrict_reblogs(opts)
|> restrict_pinned(opts)

View file

@ -794,6 +794,7 @@ def get_existing_votes(actor, %{data: %{"id" => id}}) do
query =
from(
[activity, object: object] in Activity.with_preloaded_object(Activity),
where: fragment("(?)->>'type' = 'Create'", activity.data),
where: fragment("(?)->>'actor' = ?", activity.data, ^actor),
where:
fragment(

View file

@ -132,13 +132,16 @@ def vote(user, object, choices) do
Enum.map(choices, fn index ->
answer_data = make_answer_data(user, object, Enum.at(options, index)["name"])
ActivityPub.create(%{
to: answer_data["to"],
actor: user,
context: object.data["context"],
object: answer_data,
additional: %{"cc" => answer_data["cc"]}
})
{:ok, activity} =
ActivityPub.create(%{
to: answer_data["to"],
actor: user,
context: object.data["context"],
object: answer_data,
additional: %{"cc" => answer_data["cc"]}
})
activity
end)
object = Object.get_cached_by_ap_id(object.data["id"])

View file

@ -117,7 +117,15 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
|> Enum.dedup()
info_params =
[:no_rich_text, :locked, :hide_followers, :hide_follows, :hide_favorites, :show_role]
[
:no_rich_text,
:locked,
:hide_followers,
:hide_follows,
:hide_favorites,
:show_role,
:skip_thread_containment
]
|> Enum.reduce(%{}, fn key, acc ->
add_if_present(acc, params, to_string(key), key, fn value ->
{:ok, ControllerHelper.truthy_param?(value)}

View file

@ -124,7 +124,8 @@ defp do_render("account.json", %{user: user} = opts) do
hide_followers: user.info.hide_followers,
hide_follows: user.info.hide_follows,
hide_favorites: user.info.hide_favorites,
relationship: relationship
relationship: relationship,
skip_thread_containment: user.info.skip_thread_containment
}
}
|> maybe_put_role(user, opts[:for])

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.Streamer do
use GenServer
require Logger
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Conversation.Participation
alias Pleroma.Notification
alias Pleroma.Object
@ -224,11 +225,10 @@ def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = ite
mutes = user.info.mutes || []
reblog_mutes = user.info.muted_reblogs || []
parent = Object.normalize(item)
unless is_nil(parent) or item.actor in blocks or item.actor in mutes or
item.actor in reblog_mutes or not ActivityPub.contain_activity(item, user) or
parent.data["actor"] in blocks or parent.data["actor"] in mutes do
with parent when not is_nil(parent) <- Object.normalize(item),
true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)),
true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)),
true <- thread_containment(item, user) do
send(socket.transport_pid, {:text, represent_update(item, user)})
end
else
@ -264,8 +264,8 @@ def push_to_socket(topics, topic, item) do
blocks = user.info.blocks || []
mutes = user.info.mutes || []
unless item.actor in blocks or item.actor in mutes or
not ActivityPub.contain_activity(item, user) do
with true <- Enum.all?([blocks, mutes], &(item.actor not in &1)),
true <- thread_containment(item, user) do
send(socket.transport_pid, {:text, represent_update(item, user)})
end
else
@ -279,4 +279,15 @@ defp internal_topic(topic, socket) when topic in ~w[user direct] do
end
defp internal_topic(topic, _), do: topic
@spec thread_containment(Activity.t(), User.t()) :: boolean()
defp thread_containment(_activity, %User{info: %{skip_thread_containment: true}}), do: true
defp thread_containment(activity, user) do
if Config.get([:instance, :skip_thread_containment]) do
true
else
ActivityPub.contain_activity(activity, user)
end
end
end

View file

@ -632,7 +632,15 @@ def raw_empty_array(conn, _params) do
defp build_info_cng(user, params) do
info_params =
["no_rich_text", "locked", "hide_followers", "hide_follows", "hide_favorites", "show_role"]
[
"no_rich_text",
"locked",
"hide_followers",
"hide_follows",
"hide_favorites",
"show_role",
"skip_thread_containment"
]
|> Enum.reduce(%{}, fn key, res ->
if value = params[key] do
Map.put(res, key, value == "true")

View file

@ -118,7 +118,8 @@ defp do_render("user.json", %{user: user = %User{}} = assigns) do
"pleroma" =>
%{
"confirmation_pending" => user_info.confirmation_pending,
"tags" => user.tags
"tags" => user.tags,
"skip_thread_containment" => user.info.skip_thread_containment
}
|> maybe_with_activation_status(user, for_user)
|> with_notification_settings(user, for_user)

View file

@ -157,7 +157,8 @@ defp aliases do
# * the mix environment if different than prod
defp version(version) do
{git_tag, git_pre_release} =
with {tag, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=0"]),
with {tag, 0} <-
System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true),
tag = String.trim(tag),
{describe, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=8"]),
describe = String.trim(describe),

View file

@ -86,4 +86,17 @@ test "gets all the participations for a user, ordered by updated at descending"
assert participation_one.last_activity_id == activity_three.id
end
test "Doesn't die when the conversation gets empty" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
[participation] = Participation.for_user_with_last_activity_id(user)
assert participation.last_activity_id == activity.id
{:ok, _} = CommonAPI.delete(activity.id, user)
[] = Participation.for_user_with_last_activity_id(user)
end
end

View file

@ -1,6 +1,7 @@
defmodule Pleroma.Web.ActivityPub.UtilsTest do
use Pleroma.DataCase
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
@ -204,4 +205,46 @@ test "make_json_ld_header/0" do
]
}
end
describe "get_existing_votes" do
test "fetches existing votes" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
CommonAPI.post(user, %{
"status" => "How do I pronounce LaTeX?",
"poll" => %{
"options" => ["laytekh", "lahtekh", "latex"],
"expires_in" => 20,
"multiple" => true
}
})
object = Object.normalize(activity)
{:ok, votes, object} = CommonAPI.vote(other_user, object, [0, 1])
assert Enum.sort(Utils.get_existing_votes(other_user.ap_id, object)) == Enum.sort(votes)
end
test "fetches only Create activities" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
CommonAPI.post(user, %{
"status" => "Are we living in a society?",
"poll" => %{
"options" => ["yes", "no"],
"expires_in" => 20
}
})
object = Object.normalize(activity)
{:ok, [vote], object} = CommonAPI.vote(other_user, object, [0])
vote_object = Object.normalize(vote)
{:ok, _activity, _object} = ActivityPub.like(user, vote_object)
[fetched_vote] = Utils.get_existing_votes(other_user.ap_id, object)
assert fetched_vote.id == vote.id
end
end
end

View file

@ -1,6 +1,7 @@
defmodule Pleroma.Web.ActivityPub.VisibilityTest do
use Pleroma.DataCase
alias Pleroma.Activity
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
@ -121,4 +122,46 @@ test "get_visibility", %{
test "get_visibility with directMessage flag" do
assert Visibility.get_visibility(%{data: %{"directMessage" => true}}) == "direct"
end
describe "entire_thread_visible_for_user?/2" do
test "returns false if not found activity", %{user: user} do
refute Visibility.entire_thread_visible_for_user?(%Activity{}, user)
end
test "returns true if activity hasn't 'Create' type", %{user: user} do
activity = insert(:like_activity)
assert Visibility.entire_thread_visible_for_user?(activity, user)
end
test "returns false when invalid recipients", %{user: user} do
author = insert(:user)
activity =
insert(:note_activity,
note:
insert(:note,
user: author,
data: %{"to" => ["test-user"]}
)
)
refute Visibility.entire_thread_visible_for_user?(activity, user)
end
test "returns true if user following to author" do
author = insert(:user)
user = insert(:user, following: [author.ap_id])
activity =
insert(:note_activity,
note:
insert(:note,
user: author,
data: %{"to" => [user.ap_id]}
)
)
assert Visibility.entire_thread_visible_for_user?(activity, user)
end
end
end

View file

@ -67,7 +67,8 @@ test "Represent a user account" do
hide_favorites: true,
hide_followers: false,
hide_follows: false,
relationship: %{}
relationship: %{},
skip_thread_containment: false
}
}
@ -132,7 +133,8 @@ test "Represent a Service(bot) account" do
hide_favorites: true,
hide_followers: false,
hide_follows: false,
relationship: %{}
relationship: %{},
skip_thread_containment: false
}
}
@ -233,7 +235,8 @@ test "represent an embedded relationship" do
domain_blocking: false,
showing_reblogs: true,
endorsed: false
}
},
skip_thread_containment: false
}
}

View file

@ -2539,6 +2539,19 @@ test "updates the user's hide_followers status", %{conn: conn} do
assert user["pleroma"]["hide_followers"] == true
end
test "updates the user's skip_thread_containment option", %{conn: conn} do
user = insert(:user)
response =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{skip_thread_containment: "true"})
|> json_response(200)
assert response["pleroma"]["skip_thread_containment"] == true
assert refresh_record(user).info.skip_thread_containment
end
test "updates the user's hide_follows status", %{conn: conn} do
user = insert(:user)

View file

@ -11,6 +11,16 @@ defmodule Pleroma.Web.StreamerTest do
alias Pleroma.Web.Streamer
import Pleroma.Factory
setup do
skip_thread_containment = Pleroma.Config.get([:instance, :skip_thread_containment])
on_exit(fn ->
Pleroma.Config.put([:instance, :skip_thread_containment], skip_thread_containment)
end)
:ok
end
test "it sends to public" do
user = insert(:user)
other_user = insert(:user)
@ -68,6 +78,74 @@ test "it sends to public" do
Task.await(task)
end
describe "thread_containment" do
test "it doesn't send to user if recipients invalid and thread containment is enabled" do
Pleroma.Config.put([:instance, :skip_thread_containment], false)
author = insert(:user)
user = insert(:user, following: [author.ap_id])
activity =
insert(:note_activity,
note:
insert(:note,
user: author,
data: %{"to" => ["TEST-FFF"]}
)
)
task = Task.async(fn -> refute_receive {:text, _}, 1_000 end)
fake_socket = %{transport_pid: task.pid, assigns: %{user: user}}
topics = %{"public" => [fake_socket]}
Streamer.push_to_socket(topics, "public", activity)
Task.await(task)
end
test "it sends message if recipients invalid and thread containment is disabled" do
Pleroma.Config.put([:instance, :skip_thread_containment], true)
author = insert(:user)
user = insert(:user, following: [author.ap_id])
activity =
insert(:note_activity,
note:
insert(:note,
user: author,
data: %{"to" => ["TEST-FFF"]}
)
)
task = Task.async(fn -> assert_receive {:text, _}, 1_000 end)
fake_socket = %{transport_pid: task.pid, assigns: %{user: user}}
topics = %{"public" => [fake_socket]}
Streamer.push_to_socket(topics, "public", activity)
Task.await(task)
end
test "it sends message if recipients invalid and thread containment is enabled but user's thread containment is disabled" do
Pleroma.Config.put([:instance, :skip_thread_containment], false)
author = insert(:user)
user = insert(:user, following: [author.ap_id], info: %{skip_thread_containment: true})
activity =
insert(:note_activity,
note:
insert(:note,
user: author,
data: %{"to" => ["TEST-FFF"]}
)
)
task = Task.async(fn -> assert_receive {:text, _}, 1_000 end)
fake_socket = %{transport_pid: task.pid, assigns: %{user: user}}
topics = %{"public" => [fake_socket]}
Streamer.push_to_socket(topics, "public", activity)
Task.await(task)
end
end
test "it doesn't send to blocked users" do
user = insert(:user)
blocked_user = insert(:user)

View file

@ -1495,7 +1495,7 @@ test "it sets and un-sets hide_follows", %{conn: conn} do
"hide_follows" => "false"
})
user = Repo.get!(User, user.id)
user = refresh_record(user)
assert user.info.hide_follows == false
assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
end
@ -1548,6 +1548,29 @@ test "it sets and un-sets show_role", %{conn: conn} do
assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
end
test "it sets and un-sets skip_thread_containment", %{conn: conn} do
user = insert(:user)
response =
conn
|> assign(:user, user)
|> post("/api/account/update_profile.json", %{"skip_thread_containment" => "true"})
|> json_response(200)
assert response["pleroma"]["skip_thread_containment"] == true
user = refresh_record(user)
assert user.info.skip_thread_containment
response =
conn
|> assign(:user, user)
|> post("/api/account/update_profile.json", %{"skip_thread_containment" => "false"})
|> json_response(200)
assert response["pleroma"]["skip_thread_containment"] == false
refute refresh_record(user).info.skip_thread_containment
end
test "it locks an account", %{conn: conn} do
user = insert(:user)

View file

@ -99,7 +99,8 @@ test "A user" do
"fields" => [],
"pleroma" => %{
"confirmation_pending" => false,
"tags" => []
"tags" => [],
"skip_thread_containment" => false
},
"rights" => %{"admin" => false, "delete_others_notice" => false},
"role" => "member"
@ -154,7 +155,8 @@ test "A user for a given other follower", %{user: user} do
"fields" => [],
"pleroma" => %{
"confirmation_pending" => false,
"tags" => []
"tags" => [],
"skip_thread_containment" => false
},
"rights" => %{"admin" => false, "delete_others_notice" => false},
"role" => "member"
@ -199,7 +201,8 @@ test "A user that follows you", %{user: user} do
"fields" => [],
"pleroma" => %{
"confirmation_pending" => false,
"tags" => []
"tags" => [],
"skip_thread_containment" => false
},
"rights" => %{"admin" => false, "delete_others_notice" => false},
"role" => "member"
@ -281,7 +284,8 @@ test "A blocked user for the blocker" do
"fields" => [],
"pleroma" => %{
"confirmation_pending" => false,
"tags" => []
"tags" => [],
"skip_thread_containment" => false
},
"rights" => %{"admin" => false, "delete_others_notice" => false},
"role" => "member"