Commit b2d398b1 authored by feld's avatar feld

Merge branch 'develop' into refactor/gun-pool-registry

parents 7115c5f8 00f9b53f
......@@ -25,6 +25,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mastodon API: Added `pleroma.metadata.fields_limits` to /api/v1/instance
- Mastodon API: On deletion, returns the original post text.
- Mastodon API: Add `pleroma.unread_count` to the Marker entity.
- **Breaking:** Notification Settings API for suppressing notifications
has been simplified down to `block_from_strangers`.
- **Breaking:** Notification Settings API option for hiding push notification
contents has been renamed to `hide_notification_contents`
</details>
<details>
......
......@@ -512,6 +512,7 @@
attachments_cleanup: 5,
new_users_digest: 1
],
plugins: [Oban.Plugins.Pruner],
crontab: [
{"0 0 * * *", Pleroma.Workers.Cron.ClearOauthTokenWorker},
{"0 * * * *", Pleroma.Workers.Cron.StatsWorker},
......
......@@ -287,11 +287,8 @@ See [Admin-API](admin_api.md)
* Method `PUT`
* Authentication: required
* Params:
* `followers`: BOOLEAN field, receives notifications from followers
* `follows`: BOOLEAN field, receives notifications from people the user follows
* `remote`: BOOLEAN field, receives notifications from people on remote instances
* `local`: BOOLEAN field, receives notifications from people on the local instance
* `privacy_option`: BOOLEAN field. When set to true, it removes the contents of a message from the push notification.
* `block_from_strangers`: BOOLEAN field, blocks notifications from accounts you do not follow
* `hide_notification_contents`: BOOLEAN field. When set to true, it removes the contents of a message from the push notification.
* Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}`
## `/api/pleroma/healthcheck`
......
......@@ -3,8 +3,8 @@ defmodule Mix.Tasks.Pleroma.NotificationSettings do
@moduledoc """
Example:
> mix pleroma.notification_settings --privacy-option=false --nickname-users="parallel588" # set false only for parallel588 user
> mix pleroma.notification_settings --privacy-option=true # set true for all users
> mix pleroma.notification_settings --hide-notification-contents=false --nickname-users="parallel588" # set false only for parallel588 user
> mix pleroma.notification_settings --hide-notification-contents=true # set true for all users
"""
......@@ -19,16 +19,16 @@ def run(args) do
OptionParser.parse(
args,
strict: [
privacy_option: :boolean,
hide_notification_contents: :boolean,
email_users: :string,
nickname_users: :string
]
)
privacy_option = Keyword.get(options, :privacy_option)
hide_notification_contents = Keyword.get(options, :hide_notification_contents)
if not is_nil(privacy_option) do
privacy_option
if not is_nil(hide_notification_contents) do
hide_notification_contents
|> build_query(options)
|> Pleroma.Repo.update_all([])
end
......@@ -36,15 +36,15 @@ def run(args) do
shell_info("Done")
end
defp build_query(privacy_option, options) do
defp build_query(hide_notification_contents, options) do
query =
from(u in Pleroma.User,
update: [
set: [
notification_settings:
fragment(
"jsonb_set(notification_settings, '{privacy_option}', ?)",
^privacy_option
"jsonb_set(notification_settings, '{hide_notification_contents}', ?)",
^hide_notification_contents
)
]
]
......
......@@ -571,10 +571,7 @@ def skip?(%Activity{} = activity, %User{} = user) do
[
:self,
:invisible,
:followers,
:follows,
:non_followers,
:non_follows,
:block_from_strangers,
:recently_followed,
:filtered
]
......@@ -595,45 +592,15 @@ def skip?(:invisible, %Activity{} = activity, _) do
end
def skip?(
:followers,
:block_from_strangers,
%Activity{} = activity,
%User{notification_settings: %{followers: false}} = user
) do
actor = activity.data["actor"]
follower = User.get_cached_by_ap_id(actor)
User.following?(follower, user)
end
def skip?(
:non_followers,
%Activity{} = activity,
%User{notification_settings: %{non_followers: false}} = user
%User{notification_settings: %{block_from_strangers: true}} = user
) do
actor = activity.data["actor"]
follower = User.get_cached_by_ap_id(actor)
!User.following?(follower, user)
end
def skip?(
:follows,
%Activity{} = activity,
%User{notification_settings: %{follows: false}} = user
) do
actor = activity.data["actor"]
followed = User.get_cached_by_ap_id(actor)
User.following?(user, followed)
end
def skip?(
:non_follows,
%Activity{} = activity,
%User{notification_settings: %{non_follows: false}} = user
) do
actor = activity.data["actor"]
followed = User.get_cached_by_ap_id(actor)
!User.following?(user, followed)
end
# To do: consider defining recency in hours and checking FollowingRelationship with a single SQL
def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity, %User{} = user) do
actor = activity.data["actor"]
......
......@@ -10,21 +10,15 @@ defmodule Pleroma.User.NotificationSetting do
@primary_key false
embedded_schema do
field(:followers, :boolean, default: true)
field(:follows, :boolean, default: true)
field(:non_follows, :boolean, default: true)
field(:non_followers, :boolean, default: true)
field(:privacy_option, :boolean, default: false)
field(:block_from_strangers, :boolean, default: false)
field(:hide_notification_contents, :boolean, default: false)
end
def changeset(schema, params) do
schema
|> cast(prepare_attrs(params), [
:followers,
:follows,
:non_follows,
:non_followers,
:privacy_option
:block_from_strangers,
:hide_notification_contents
])
end
......
......@@ -60,7 +60,7 @@ def filter(%{"type" => "Follow", "actor" => actor_id} = message) do
if score < 0.8 do
{:ok, message}
else
{:reject, nil}
{:reject, "[AntiFollowbotPolicy] Scored #{actor_id} as #{score}"}
end
end
......
......@@ -39,14 +39,13 @@ def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message
{:ok, message}
{:old_user, false} ->
{:reject, nil}
{:reject, "[AntiLinkSpamPolicy] User has no posts nor followers"}
{:error, _} ->
{:reject, nil}
{:reject, "[AntiLinkSpamPolicy] Failed to get or fetch user by ap_id"}
e ->
Logger.warn("[MRF anti-link-spam] WTF: unhandled error #{inspect(e)}")
{:reject, nil}
{:reject, "[AntiLinkSpamPolicy] Unhandled error #{inspect(e)}"}
end
end
......
......@@ -43,7 +43,7 @@ defp delist_message(message, _threshold), do: {:ok, message}
defp reject_message(message, threshold) when threshold > 0 do
with {_, recipients} <- get_recipient_count(message) do
if recipients > threshold do
{:reject, nil}
{:reject, "[HellthreadPolicy] #{recipients} recipients is over the limit of #{threshold}"}
else
{:ok, message}
end
......@@ -87,7 +87,7 @@ def filter(%{"type" => "Create", "object" => %{"type" => object_type}} = message
{:ok, message} <- delist_message(message, delist_threshold) do
{:ok, message}
else
_e -> {:reject, nil}
e -> e
end
end
......
......@@ -24,7 +24,7 @@ defp check_reject(%{"object" => %{"content" => content, "summary" => summary}} =
if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern ->
string_matches?(content, pattern) or string_matches?(summary, pattern)
end) do
{:reject, nil}
{:reject, "[KeywordPolicy] Matches with rejected keyword"}
else
{:ok, message}
end
......@@ -89,8 +89,9 @@ def filter(%{"type" => "Create", "object" => %{"content" => _content}} = message
{:ok, message} <- check_replace(message) do
{:ok, message}
else
_e ->
{:reject, nil}
{:reject, nil} -> {:reject, "[KeywordPolicy] "}
{:reject, _} = e -> e
_e -> {:reject, "[KeywordPolicy] "}
end
end
......
......@@ -12,8 +12,9 @@ def filter(%{"type" => "Create"} = message) do
reject_actors = Pleroma.Config.get([:mrf_mention, :actors], [])
recipients = (message["to"] || []) ++ (message["cc"] || [])
if Enum.any?(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do
{:reject, nil}
if rejected_mention =
Enum.find(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do
{:reject, "[MentionPolicy] Rejected for mention of #{rejected_mention}"}
else
{:ok, message}
end
......
......@@ -28,7 +28,7 @@ defp check_date(%{"object" => %{"published" => published}} = message) do
defp check_reject(message, actions) do
if :reject in actions do
{:reject, nil}
{:reject, "[ObjectAgePolicy]"}
else
{:ok, message}
end
......@@ -47,9 +47,8 @@ defp check_delist(message, actions) do
{:ok, message}
else
# Unhandleable error: somebody is messing around, just drop the message.
_e ->
{:reject, nil}
{:reject, "[ObjectAgePolicy] Unhandled error"}
end
else
{:ok, message}
......@@ -69,9 +68,8 @@ defp check_strip_followers(message, actions) do
{:ok, message}
else
# Unhandleable error: somebody is messing around, just drop the message.
_e ->
{:reject, nil}
{:reject, "[ObjectAgePolicy] Unhandled error"}
end
else
{:ok, message}
......
......@@ -38,7 +38,7 @@ def filter(%{"type" => "Create"} = object) do
{:ok, object}
true ->
{:reject, nil}
{:reject, "[RejectNonPublic] visibility: #{visibility}"}
end
end
......
......@@ -21,7 +21,7 @@ defp check_accept(%{host: actor_host} = _actor_info, object) do
accepts == [] -> {:ok, object}
actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
MRF.subdomain_match?(accepts, actor_host) -> {:ok, object}
true -> {:reject, nil}
true -> {:reject, "[SimplePolicy] host not in accept list"}
end
end
......@@ -31,7 +31,7 @@ defp check_reject(%{host: actor_host} = _actor_info, object) do
|> MRF.subdomains_regex()
if MRF.subdomain_match?(rejects, actor_host) do
{:reject, nil}
{:reject, "[SimplePolicy] host in reject list"}
else
{:ok, object}
end
......@@ -114,7 +114,7 @@ defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"}
|> MRF.subdomains_regex()
if MRF.subdomain_match?(report_removal, actor_host) do
{:reject, nil}
{:reject, "[SimplePolicy] host in report_removal list"}
else
{:ok, object}
end
......@@ -159,7 +159,7 @@ def filter(%{"type" => "Delete", "actor" => actor} = object) do
|> MRF.subdomains_regex()
if MRF.subdomain_match?(reject_deletes, actor_host) do
{:reject, nil}
{:reject, "[SimplePolicy] host in reject_deletes list"}
else
{:ok, object}
end
......@@ -177,7 +177,9 @@ def filter(%{"actor" => actor} = object) do
{:ok, object} <- check_report_removal(actor_info, object) do
{:ok, object}
else
_e -> {:reject, nil}
{:reject, nil} -> {:reject, "[SimplePolicy]"}
{:reject, _} = e -> e
_ -> {:reject, "[SimplePolicy]"}
end
end
......@@ -191,7 +193,9 @@ def filter(%{"id" => actor, "type" => obj_type} = object)
{:ok, object} <- check_banner_removal(actor_info, object) do
{:ok, object}
else
_e -> {:reject, nil}
{:reject, nil} -> {:reject, "[SimplePolicy]"}
{:reject, _} = e -> e
_ -> {:reject, "[SimplePolicy]"}
end
end
......
......@@ -134,12 +134,13 @@ defp process_tag(
if user.local == true do
{:ok, message}
else
{:reject, nil}
{:reject,
"[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-remote-subscription"}
end
end
defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow"}),
do: {:reject, nil}
defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow", "actor" => actor}),
do: {:reject, "[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-any-subscription"}
defp process_tag(_, message), do: {:ok, message}
......
......@@ -14,7 +14,7 @@ defp filter_by_list(%{"actor" => actor} = object, allow_list) do
if actor in allow_list do
{:ok, object}
else
{:reject, nil}
{:reject, "[UserAllowListPolicy] #{actor} not in the list"}
end
end
......
......@@ -11,22 +11,26 @@ def filter(%{"type" => "Undo", "object" => child_message} = message) do
with {:ok, _} <- filter(child_message) do
{:ok, message}
else
{:reject, nil} ->
{:reject, nil}
{:reject, _} = e -> e
end
end
def filter(%{"type" => message_type} = message) do
with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
true <-
Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type),
false <-
length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type),
{_, true} <-
{:accepted,
Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type)},
{_, false} <-
{:rejected,
length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type)},
{:ok, _} <- filter(message["object"]) do
{:ok, message}
else
_ -> {:reject, nil}
{:reject, _} = e -> e
{:accepted, _} -> {:reject, "[VocabularyPolicy] #{message_type} not in accept list"}
{:rejected, _} -> {:reject, "[VocabularyPolicy] #{message_type} in reject list"}
_ -> {:reject, "[VocabularyPolicy]"}
end
end
......
......@@ -49,7 +49,8 @@ def is_representable?(%Activity{} = activity) do
"""
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
Logger.debug("Federating #{id} to #{inbox}")
%{host: host, path: path} = URI.parse(inbox)
uri = URI.parse(inbox)
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
......@@ -57,8 +58,8 @@ def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = pa
signature =
Pleroma.Signature.sign(actor, %{
"(request-target)": "post #{path}",
host: host,
"(request-target)": "post #{uri.path}",
host: signature_host(uri),
"content-length": byte_size(json),
digest: digest,
date: date
......@@ -76,8 +77,9 @@ def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = pa
{"digest", digest}
]
) do
if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
do: Instances.set_reachable(inbox)
if not Map.has_key?(params, :unreachable_since) || params[:unreachable_since] do
Instances.set_reachable(inbox)
end
result
else
......@@ -96,6 +98,14 @@ def publish_one(%{actor_id: actor_id} = params) do
|> publish_one()
end
defp signature_host(%URI{port: port, scheme: scheme, host: host}) do
if port == URI.default_port(scheme) do
host
else
"#{host}:#{port}"
end
end
defp should_federate?(inbox, public) do
if public do
true
......
......@@ -90,11 +90,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
notification_settings: %Schema{
type: :object,
properties: %{
followers: %Schema{type: :boolean},
follows: %Schema{type: :boolean},
non_followers: %Schema{type: :boolean},
non_follows: %Schema{type: :boolean},
privacy_option: %Schema{type: :boolean}
block_from_strangers: %Schema{type: :boolean},
hide_notification_contents: %Schema{type: :boolean}
}
},
relationship: AccountRelationship,
......@@ -182,11 +179,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
"unread_conversation_count" => 0,
"tags" => [],
"notification_settings" => %{
"followers" => true,
"follows" => true,
"non_followers" => true,
"non_follows" => true,
"privacy_option" => false
"block_from_strangers" => false,
"hide_notification_contents" => false
},
"relationship" => %{
"blocked_by" => false,
......
......@@ -172,6 +172,11 @@ def create(%{assigns: %{user: user}, body_params: %{status: _} = params} = conn,
with_direct_conversation_id: true
)
else
{:error, {:reject, message}} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{error: message})
{:error, message} ->
conn
|> put_status(:unprocessable_entity)
......
......@@ -104,7 +104,7 @@ def build_content(notification, actor, object, mastodon_type \\ nil)
def build_content(
%{
user: %{notification_settings: %{privacy_option: true}}
user: %{notification_settings: %{hide_notification_contents: true}}
} = notification,
_actor,
_object,
......
defmodule Pleroma.Repo.Migrations.RenameNotificationPrivacyOption do
use Ecto.Migration
def up do
execute(
"UPDATE users SET notification_settings = notification_settings - 'privacy_option' || jsonb_build_object('hide_notification_contents', notification_settings->'privacy_option')
where notification_settings ? 'privacy_option'
and local"
)
end
def down do
execute(
"UPDATE users SET notification_settings = notification_settings - 'hide_notification_contents' || jsonb_build_object('privacy_option', notification_settings->'hide_notification_contents')
where notification_settings ? 'hide_notification_contents'
and local"
)
end
end
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/app.77b1644622e3bae24b6b.css rel=stylesheet><link href=/static/fontello.1594374054351.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.247dc52c7abe6a0dab87.js></script><script type=text/javascript src=/static/js/app.1e68e208590653dab5aa.js></script></body></html>
\ No newline at end of file
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/app.6dbc7dea4fc148c85860.css rel=stylesheet><link href=/static/fontello.1594823398494.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.9e24ed238da5a8538f50.js></script><script type=text/javascript src=/static/js/app.31bba9f1e242ff273dcb.js></script></body></html>
\ No newline at end of file
......@@ -243,4 +243,4 @@
cursor: pointer;
}
/*# sourceMappingURL=app.77b1644622e3bae24b6b.css.map*/
\ No newline at end of file
/*# sourceMappingURL=app.6dbc7dea4fc148c85860.css.map*/
\ No newline at end of file
@font-face {
font-family: "Icons";
src: url("./font/fontello.1589385935077.eot");
src: url("./font/fontello.1589385935077.eot") format("embedded-opentype"),
url("./font/fontello.1589385935077.woff2") format("woff2"),
url("./font/fontello.1589385935077.woff") format("woff"),
url("./font/fontello.1589385935077.ttf") format("truetype"),
url("./font/fontello.1589385935077.svg") format("svg");
font-weight: normal;
font-style: normal;
}
[class^="icon-"]::before,
[class*=" icon-"]::before {
font-family: "Icons";
font-style: normal;
font-weight: normal;
speak: none;