Commit 139b9d13 authored by lain's avatar lain
Browse files

Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into remake-remodel-dms

parents 970b7438 918a8094
Pipeline #24656 passed with stages
in 3 minutes and 54 seconds
......@@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
- New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required.
- Mix task to create trusted OAuth App.
- Notifications: Added `follow_request` notification type (configurable, see `[:notifications, :enable_follow_request_notifications]` setting).
<details>
<summary>API Changes</summary>
- Mastodon API: Support for `include_types` in `/api/v1/notifications`.
......@@ -26,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [unreleased-patch]
### Fixed
- Logger configuration through AdminFE
- HTTP Basic Authentication permissions issue
### Added
<details>
......
......@@ -561,6 +561,8 @@
inactivity_threshold: 7
}
config :pleroma, :notifications, enable_follow_request_notifications: false
config :pleroma, :oauth2,
token_expires_in: 600,
issue_new_refresh_token: true,
......
......@@ -2267,6 +2267,20 @@
}
]
},
%{
group: :pleroma,
key: :notifications,
type: :group,
description: "Notification settings",
children: [
%{
key: :enable_follow_request_notifications,
type: :boolean,
description:
"Enables notifications on new follow requests (causes issues with older PleromaFE versions)."
}
]
},
%{
group: :pleroma,
key: Pleroma.Emails.UserEmail,
......
......@@ -27,17 +27,13 @@ defmodule Pleroma.Activity do
# https://github.com/tootsuite/mastodon/blob/master/app/models/notification.rb#L19
@mastodon_notification_types %{
"Create" => "mention",
"Follow" => "follow",
"Follow" => ["follow", "follow_request"],
"Announce" => "reblog",
"Like" => "favourite",
"Move" => "move",
"EmojiReact" => "pleroma:emoji_reaction"
}
@mastodon_to_ap_notification_types for {k, v} <- @mastodon_notification_types,
into: %{},
do: {v, k}
schema "activities" do
field(:data, :map)
field(:local, :boolean, default: true)
......@@ -291,15 +287,43 @@ defp purge_web_resp_cache(%Activity{} = activity) do
defp purge_web_resp_cache(nil), do: nil
for {ap_type, type} <- @mastodon_notification_types do
def follow_accepted?(
%Activity{data: %{"type" => "Follow", "object" => followed_ap_id}} = activity
) do
with %User{} = follower <- Activity.user_actor(activity),
%User{} = followed <- User.get_cached_by_ap_id(followed_ap_id) do
Pleroma.FollowingRelationship.following?(follower, followed)
else
_ -> false
end
end
def follow_accepted?(_), do: false
@spec mastodon_notification_type(Activity.t()) :: String.t() | nil
for {ap_type, type} <- @mastodon_notification_types, not is_list(type) do
def mastodon_notification_type(%Activity{data: %{"type" => unquote(ap_type)}}),
do: unquote(type)
end
def mastodon_notification_type(%Activity{data: %{"type" => "Follow"}} = activity) do
if follow_accepted?(activity) do
"follow"
else
"follow_request"
end
end
def mastodon_notification_type(%Activity{}), do: nil
@spec from_mastodon_notification_type(String.t()) :: String.t() | nil
@doc "Converts Mastodon notification type to AR activity type"
def from_mastodon_notification_type(type) do
Map.get(@mastodon_to_ap_notification_types, type)
with {k, _v} <-
Enum.find(@mastodon_notification_types, fn {_k, v} -> type in List.wrap(v) end) do
k
end
end
def all_by_actor_and_id(actor, status_ids \\ [])
......
......@@ -284,8 +284,17 @@ def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = act
end
end
def create_notifications(%Activity{data: %{"type" => "Follow"}} = activity) do
if Pleroma.Config.get([:notifications, :enable_follow_request_notifications]) ||
Activity.follow_accepted?(activity) do
do_create_notifications(activity)
else
{:ok, []}
end
end
def create_notifications(%Activity{data: %{"type" => type}} = activity)
when type in ["Like", "Announce", "Follow", "Move", "EmojiReact"] do
when type in ["Like", "Announce", "Move", "EmojiReact"] do
do_create_notifications(activity)
end
......
......@@ -4,8 +4,11 @@
defmodule Pleroma.Plugs.AuthenticationPlug do
alias Comeonin.Pbkdf2
import Plug.Conn
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
import Plug.Conn
require Logger
def init(options), do: options
......@@ -37,6 +40,7 @@ def call(
if Pbkdf2.checkpw(password, password_hash) do
conn
|> assign(:user, auth_user)
|> OAuthScopesPlug.skip_plug()
else
conn
end
......
......@@ -4,6 +4,8 @@
defmodule Pleroma.Plugs.LegacyAuthenticationPlug do
import Plug.Conn
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
def init(options) do
......@@ -27,6 +29,7 @@ def call(
conn
|> assign(:auth_user, user)
|> assign(:user, user)
|> OAuthScopesPlug.skip_plug()
else
_ ->
conn
......
......@@ -5,30 +5,32 @@
defmodule Pleroma.Plugs.PlugHelper do
@moduledoc "Pleroma Plug helper"
def append_to_called_plugs(conn, plug_module) do
append_to_private_list(conn, :called_plugs, plug_module)
end
@called_plugs_list_id :called_plugs
def called_plugs_list_id, do: @called_plugs_list_id
def append_to_skipped_plugs(conn, plug_module) do
append_to_private_list(conn, :skipped_plugs, plug_module)
end
@skipped_plugs_list_id :skipped_plugs
def skipped_plugs_list_id, do: @skipped_plugs_list_id
@doc "Returns `true` if specified plug was called."
def plug_called?(conn, plug_module) do
contained_in_private_list?(conn, :called_plugs, plug_module)
contained_in_private_list?(conn, @called_plugs_list_id, plug_module)
end
@doc "Returns `true` if specified plug was explicitly marked as skipped."
def plug_skipped?(conn, plug_module) do
contained_in_private_list?(conn, :skipped_plugs, plug_module)
contained_in_private_list?(conn, @skipped_plugs_list_id, plug_module)
end
@doc "Returns `true` if specified plug was either called or explicitly marked as skipped."
def plug_called_or_skipped?(conn, plug_module) do
plug_called?(conn, plug_module) || plug_skipped?(conn, plug_module)
end
defp append_to_private_list(conn, private_variable, value) do
list = conn.private[private_variable] || []
# Appends plug to known list (skipped, called). Intended to be used from within plug code only.
def append_to_private_list(conn, list_id, value) do
list = conn.private[list_id] || []
modified_list = Enum.uniq(list ++ [value])
Plug.Conn.put_private(conn, private_variable, modified_list)
Plug.Conn.put_private(conn, list_id, modified_list)
end
defp contained_in_private_list?(conn, private_variable, value) do
......
......@@ -688,6 +688,8 @@ def needs_update?(%User{local: false} = user) do
def needs_update?(_), do: true
@spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
# "Locked" (self-locked) users demand explicit authorization of follow requests
def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
follow(follower, followed, :follow_pending)
end
......
......@@ -1444,7 +1444,10 @@ defp object_to_user_data(data) do
emojis =
data
|> Map.get("tag", [])
|> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end)
|> Enum.filter(fn
%{"type" => "Emoji"} -> true
_ -> false
end)
|> Enum.reduce(%{}, fn %{"icon" => %{"url" => url}, "name" => name}, acc ->
Map.put(acc, String.trim(name, ":"), url)
end)
......
......@@ -19,7 +19,9 @@ def handle(object, meta \\ [])
def handle(%{data: %{"type" => "Like"}} = object, meta) do
liked_object = Object.get_by_ap_id(object.data["object"])
Utils.add_like_to_object(object, liked_object)
Notification.create_notifications(object)
{:ok, object, meta}
end
......
......@@ -133,9 +133,6 @@ def render(
# Note: :skip_relationships option being applied to _account_ rendering (here)
put_target(response, activity, reading_user, render_opts)
"follow" ->
response
"pleroma:emoji_reaction" ->
response
|> put_status(parent_activity_fn.(), reading_user, render_opts)
......@@ -144,6 +141,9 @@ def render(
"pleroma:chat_mention" ->
put_chat_message(response, activity, reading_user, render_opts)
type when type in ["follow", "follow_request"] ->
response
_ ->
nil
end
......
......@@ -16,6 +16,8 @@ defmodule Pleroma.Web.Push.Impl do
require Logger
import Ecto.Query
defdelegate mastodon_notification_type(activity), to: Activity
@types ["Create", "Follow", "Announce", "Like", "Move"]
@doc "Performs sending notifications for user subscriptions"
......@@ -24,32 +26,32 @@ def perform(
%{
activity: %{data: %{"type" => activity_type}} = activity,
user: %User{id: user_id}
} = notif
} = notification
)
when activity_type in @types do
actor = User.get_cached_by_ap_id(notif.activity.data["actor"])
actor = User.get_cached_by_ap_id(notification.activity.data["actor"])
type = Activity.mastodon_notification_type(notif.activity)
mastodon_type = mastodon_notification_type(notification.activity)
gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key)
avatar_url = User.avatar_url(actor)
object = Object.normalize(activity)
user = User.get_cached_by_id(user_id)
direct_conversation_id = Activity.direct_conversation_id(activity, user)
for subscription <- fetch_subsriptions(user_id),
get_in(subscription.data, ["alerts", type]) do
for subscription <- fetch_subscriptions(user_id),
Subscription.enabled?(subscription, mastodon_type) do
%{
access_token: subscription.token.token,
notification_id: notif.id,
notification_type: type,
notification_id: notification.id,
notification_type: mastodon_type,
icon: avatar_url,
preferred_locale: "en",
pleroma: %{
activity_id: notif.activity.id,
activity_id: notification.activity.id,
direct_conversation_id: direct_conversation_id
}
}
|> Map.merge(build_content(notif, actor, object))
|> Map.merge(build_content(notification, actor, object, mastodon_type))
|> Jason.encode!()
|> push_message(build_sub(subscription), gcm_api_key, subscription)
end
......@@ -82,7 +84,7 @@ def push_message(body, sub, api_key, subscription) do
end
@doc "Gets user subscriptions"
def fetch_subsriptions(user_id) do
def fetch_subscriptions(user_id) do
Subscription
|> where(user_id: ^user_id)
|> preload(:token)
......@@ -99,28 +101,36 @@ def build_sub(subscription) do
}
end
def build_content(notification, actor, object, mastodon_type \\ nil)
def build_content(
%{
activity: %{data: %{"directMessage" => true}},
user: %{notification_settings: %{privacy_option: true}}
},
actor,
_
_object,
_mastodon_type
) do
%{title: "New Direct Message", body: "@#{actor.nickname}"}
end
def build_content(notif, actor, object) do
def build_content(notification, actor, object, mastodon_type) do
mastodon_type = mastodon_type || mastodon_notification_type(notification.activity)
%{
title: format_title(notif),
body: format_body(notif, actor, object)
title: format_title(notification, mastodon_type),
body: format_body(notification, actor, object, mastodon_type)
}
end
def format_body(activity, actor, object, mastodon_type \\ nil)
def format_body(
%{activity: %{data: %{"type" => "Create"}}},
actor,
%{data: %{"content" => content}}
%{data: %{"content" => content}},
_mastodon_type
) do
"@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}"
end
......@@ -128,33 +138,44 @@ def format_body(
def format_body(
%{activity: %{data: %{"type" => "Announce"}}},
actor,
%{data: %{"content" => content}}
%{data: %{"content" => content}},
_mastodon_type
) do
"@#{actor.nickname} repeated: #{Utils.scrub_html_and_truncate(content, 80)}"
end
def format_body(
%{activity: %{data: %{"type" => type}}},
%{activity: %{data: %{"type" => type}}} = notification,
actor,
_object
_object,
mastodon_type
)
when type in ["Follow", "Like"] do
case type do
"Follow" -> "@#{actor.nickname} has followed you"
"Like" -> "@#{actor.nickname} has favorited your post"
mastodon_type = mastodon_type || mastodon_notification_type(notification.activity)
case mastodon_type do
"follow" -> "@#{actor.nickname} has followed you"
"follow_request" -> "@#{actor.nickname} has requested to follow you"
"favourite" -> "@#{actor.nickname} has favorited your post"
end
end
def format_title(%{activity: %{data: %{"directMessage" => true}}}) do
def format_title(activity, mastodon_type \\ nil)
def format_title(%{activity: %{data: %{"directMessage" => true}}}, _mastodon_type) do
"New Direct Message"
end
def format_title(%{activity: %{data: %{"type" => type}}}) do
case type do
"Create" -> "New Mention"
"Follow" -> "New Follower"
"Announce" -> "New Repeat"
"Like" -> "New Favorite"
def format_title(%{activity: activity}, mastodon_type) do
mastodon_type = mastodon_type || mastodon_notification_type(activity)
case mastodon_type do
"mention" -> "New Mention"
"follow" -> "New Follower"
"follow_request" -> "New Follow Request"
"reblog" -> "New Repeat"
"favourite" -> "New Favorite"
type -> "New #{String.capitalize(type || "event")}"
end
end
end
......@@ -32,6 +32,14 @@ defp alerts(%{"data" => %{"alerts" => alerts}}) do
%{"alerts" => alerts}
end
def enabled?(subscription, "follow_request") do
enabled?(subscription, "follow")
end
def enabled?(subscription, alert_type) do
get_in(subscription.data, ["alerts", alert_type])
end
def create(
%User{} = user,
%Token{} = token,
......
......@@ -40,17 +40,22 @@ defp set_put_layout(conn, _) do
# Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain
defp skip_plug(conn, plug_module) do
try do
plug_module.ensure_skippable()
plug_module.skip_plug(conn)
rescue
UndefinedFunctionError ->
raise "#{plug_module} is not skippable. Append `use Pleroma.Web, :plug` to its code."
end
PlugHelper.append_to_skipped_plugs(conn, plug_module)
end
# Here we can apply before-action hooks (e.g. verify whether auth checks were preformed)
# Executed just before actual controller action, invokes before-action hooks (callbacks)
defp action(conn, params) do
with %Plug.Conn{halted: false} <- maybe_halt_on_missing_oauth_scopes_check(conn) do
super(conn, params)
end
end
# Halts if authenticated API action neither performs nor explicitly skips OAuth scopes check
defp maybe_halt_on_missing_oauth_scopes_check(conn) do
if Pleroma.Plugs.AuthExpectedPlug.auth_expected?(conn) &&
not PlugHelper.plug_called_or_skipped?(conn, Pleroma.Plugs.OAuthScopesPlug) do
conn
......@@ -60,7 +65,7 @@ defp action(conn, params) do
)
|> halt()
else
super(conn, params)
conn
end
end
end
......@@ -129,7 +134,16 @@ def plug do
quote do
alias Pleroma.Plugs.PlugHelper
def ensure_skippable, do: :noop
@doc """
Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain.
"""
def skip_plug(conn) do
PlugHelper.append_to_private_list(
conn,
PlugHelper.skipped_plugs_list_id(),
__MODULE__
)
end
@impl Plug
@doc "If marked as skipped, returns `conn`, and calls `perform/2` otherwise."
......@@ -138,7 +152,7 @@ def call(%Plug.Conn{} = conn, options) do
conn
else
conn
|> PlugHelper.append_to_called_plugs(__MODULE__)
|> PlugHelper.append_to_private_list(PlugHelper.called_plugs_list_id(), __MODULE__)
|> perform(options)
end
end
......
......@@ -221,19 +221,26 @@ defp version(version) do
identifier_filter = ~r/[^0-9a-z\-]+/i
# Pre-release version, denoted from patch version with a hyphen
{tag, tag_err} =
System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true)
{describe, describe_err} = System.cmd("git", ["describe", "--tags", "--abbrev=8"])
{commit_hash, commit_hash_err} = System.cmd("git", ["rev-parse", "--short", "HEAD"])
git_pre_release =
with {tag, 0} <-
System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true),
{describe, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=8"]) do
describe
|> String.trim()
|> String.replace(String.trim(tag), "")
|> String.trim_leading("-")
|> String.trim()
else
_ ->
{commit_hash, 0} = System.cmd("git", ["rev-parse", "--short", "HEAD"])
cond do
tag_err == 0 and describe_err == 0 ->
describe
|> String.trim()
|> String.replace(String.trim(tag), "")
|> String.trim_leading("-")
|> String.trim()
commit_hash_err == 0 ->
"0-g" <> String.trim(commit_hash)
true ->
""
end
# Branch name as pre-release version component, denoted with a dot
......@@ -251,6 +258,8 @@ defp version(version) do
|> String.replace(identifier_filter, "-")
branch_name
else
_ -> "stable"
end
build_name =
......
......@@ -17,7 +17,10 @@ def up do
emoji =
user.source_data
|> Map.get("tag", [])
|> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end)
|> Enum.filter(fn
%{"type" => "Emoji"} -> true
_ -> false
end)
|> Enum.reduce(%{}, fn %{"icon" => %{"url" => url}, "name" => name}, acc ->
Map.put(acc, String.trim(name, ":"), url)
end)
......
This diff is collapsed.
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment