diff --git a/CHANGELOG.md b/CHANGELOG.md index 2239a5288..9270e801f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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).
API Changes - 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
diff --git a/config/config.exs b/config/config.exs index 7f013aaad..f44837e24 100644 --- a/config/config.exs +++ b/config/config.exs @@ -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, diff --git a/config/description.exs b/config/description.exs index 642f1a3ce..b1938912c 100644 --- a/config/description.exs +++ b/config/description.exs @@ -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, diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 5a8329e69..6213d0eb7 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -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 \\ []) diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 04ee510b9..73e19bf97 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -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 diff --git a/lib/pleroma/plugs/authentication_plug.ex b/lib/pleroma/plugs/authentication_plug.ex index 089028d77..0061c69dc 100644 --- a/lib/pleroma/plugs/authentication_plug.ex +++ b/lib/pleroma/plugs/authentication_plug.ex @@ -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 diff --git a/lib/pleroma/plugs/legacy_authentication_plug.ex b/lib/pleroma/plugs/legacy_authentication_plug.ex index 5c5c36c56..d346e01a6 100644 --- a/lib/pleroma/plugs/legacy_authentication_plug.ex +++ b/lib/pleroma/plugs/legacy_authentication_plug.ex @@ -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 diff --git a/lib/pleroma/plugs/plug_helper.ex b/lib/pleroma/plugs/plug_helper.ex index 4f83e9414..9c67be8ef 100644 --- a/lib/pleroma/plugs/plug_helper.ex +++ b/lib/pleroma/plugs/plug_helper.ex @@ -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 diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 896bab140..bef4679cb 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -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 diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 5ee777acf..f644897c2 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -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) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index f32a99ec4..2effb74f8 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -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 diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index 0b05d178b..2a9951831 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -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 diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index afa510f08..f1740a6e0 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -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 diff --git a/lib/pleroma/web/push/subscription.ex b/lib/pleroma/web/push/subscription.ex index 5c448d6c9..b99b0c5fb 100644 --- a/lib/pleroma/web/push/subscription.ex +++ b/lib/pleroma/web/push/subscription.ex @@ -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, diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex index ae7c94640..bf48ce26c 100644 --- a/lib/pleroma/web/web.ex +++ b/lib/pleroma/web/web.ex @@ -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 diff --git a/mix.exs b/mix.exs index c5e5fd432..b76aef180 100644 --- a/mix.exs +++ b/mix.exs @@ -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 = diff --git a/priv/repo/migrations/20200406100225_users_add_emoji.exs b/priv/repo/migrations/20200406100225_users_add_emoji.exs index 9f57abb5c..f248108de 100644 --- a/priv/repo/migrations/20200406100225_users_add_emoji.exs +++ b/priv/repo/migrations/20200406100225_users_add_emoji.exs @@ -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) diff --git a/priv/static/font/fontello.1575660578688.eot b/priv/static/font/fontello.1575660578688.eot deleted file mode 100644 index 31a66127f..000000000 Binary files a/priv/static/font/fontello.1575660578688.eot and /dev/null differ diff --git a/priv/static/font/fontello.1575660578688.svg b/priv/static/font/fontello.1575660578688.svg deleted file mode 100644 index 19fa56ba4..000000000 --- a/priv/static/font/fontello.1575660578688.svg +++ /dev/null @@ -1,126 +0,0 @@ - - - -Copyright (C) 2019 by original authors @ fontello.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/priv/static/font/fontello.1575660578688.ttf b/priv/static/font/fontello.1575660578688.ttf deleted file mode 100644 index 7e990495e..000000000 Binary files a/priv/static/font/fontello.1575660578688.ttf and /dev/null differ diff --git a/priv/static/font/fontello.1575660578688.woff b/priv/static/font/fontello.1575660578688.woff deleted file mode 100644 index 239190cba..000000000 Binary files a/priv/static/font/fontello.1575660578688.woff and /dev/null differ diff --git a/priv/static/font/fontello.1575660578688.woff2 b/priv/static/font/fontello.1575660578688.woff2 deleted file mode 100644 index b4d3537c5..000000000 Binary files a/priv/static/font/fontello.1575660578688.woff2 and /dev/null differ diff --git a/priv/static/font/fontello.1575662648966.eot b/priv/static/font/fontello.1575662648966.eot deleted file mode 100644 index a5cb925ad..000000000 Binary files a/priv/static/font/fontello.1575662648966.eot and /dev/null differ diff --git a/priv/static/font/fontello.1575662648966.svg b/priv/static/font/fontello.1575662648966.svg deleted file mode 100644 index 19fa56ba4..000000000 --- a/priv/static/font/fontello.1575662648966.svg +++ /dev/null @@ -1,126 +0,0 @@ - - - -Copyright (C) 2019 by original authors @ fontello.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/priv/static/font/fontello.1575662648966.ttf b/priv/static/font/fontello.1575662648966.ttf deleted file mode 100644 index ec67a3d00..000000000 Binary files a/priv/static/font/fontello.1575662648966.ttf and /dev/null differ diff --git a/priv/static/font/fontello.1575662648966.woff b/priv/static/font/fontello.1575662648966.woff deleted file mode 100644 index feee99308..000000000 Binary files a/priv/static/font/fontello.1575662648966.woff and /dev/null differ diff --git a/priv/static/font/fontello.1575662648966.woff2 b/priv/static/font/fontello.1575662648966.woff2 deleted file mode 100644 index a126c585f..000000000 Binary files a/priv/static/font/fontello.1575662648966.woff2 and /dev/null differ diff --git a/priv/static/fontello.1575660578688.css b/priv/static/fontello.1575660578688.css deleted file mode 100644 index f232f5600..000000000 Binary files a/priv/static/fontello.1575660578688.css and /dev/null differ diff --git a/priv/static/fontello.1575662648966.css b/priv/static/fontello.1575662648966.css deleted file mode 100644 index a47f73e3a..000000000 Binary files a/priv/static/fontello.1575662648966.css and /dev/null differ diff --git a/priv/static/index.html b/priv/static/index.html index 4304bdcbb..66c9b53de 100644 --- a/priv/static/index.html +++ b/priv/static/index.html @@ -1 +1 @@ -Pleroma
\ No newline at end of file +Pleroma
\ No newline at end of file diff --git a/priv/static/static/font/fontello.1583594169021.woff2 b/priv/static/static/font/fontello.1583594169021.woff2 deleted file mode 100644 index b963e9489..000000000 Binary files a/priv/static/static/font/fontello.1583594169021.woff2 and /dev/null differ diff --git a/priv/static/static/font/fontello.1583594169021.eot b/priv/static/static/font/fontello.1587147224637.eot similarity index 99% rename from priv/static/static/font/fontello.1583594169021.eot rename to priv/static/static/font/fontello.1587147224637.eot index f822a48a3..523e14f27 100644 Binary files a/priv/static/static/font/fontello.1583594169021.eot and b/priv/static/static/font/fontello.1587147224637.eot differ diff --git a/priv/static/static/font/fontello.1583594169021.svg b/priv/static/static/font/fontello.1587147224637.svg similarity index 100% rename from priv/static/static/font/fontello.1583594169021.svg rename to priv/static/static/font/fontello.1587147224637.svg diff --git a/priv/static/static/font/fontello.1583594169021.ttf b/priv/static/static/font/fontello.1587147224637.ttf similarity index 99% rename from priv/static/static/font/fontello.1583594169021.ttf rename to priv/static/static/font/fontello.1587147224637.ttf index 5ed36e9aa..ec6f7f9b4 100644 Binary files a/priv/static/static/font/fontello.1583594169021.ttf and b/priv/static/static/font/fontello.1587147224637.ttf differ diff --git a/priv/static/static/font/fontello.1583594169021.woff b/priv/static/static/font/fontello.1587147224637.woff similarity index 98% rename from priv/static/static/font/fontello.1583594169021.woff rename to priv/static/static/font/fontello.1587147224637.woff index 408c26afb..da56c9221 100644 Binary files a/priv/static/static/font/fontello.1583594169021.woff and b/priv/static/static/font/fontello.1587147224637.woff differ diff --git a/priv/static/static/font/fontello.1587147224637.woff2 b/priv/static/static/font/fontello.1587147224637.woff2 new file mode 100644 index 000000000..6192c0f22 Binary files /dev/null and b/priv/static/static/font/fontello.1587147224637.woff2 differ diff --git a/priv/static/static/fontello.1583594169021.css b/priv/static/static/fontello.1587147224637.css similarity index 89% rename from priv/static/static/fontello.1583594169021.css rename to priv/static/static/fontello.1587147224637.css index c096e6103..48e6a5b3c 100644 Binary files a/priv/static/static/fontello.1583594169021.css and b/priv/static/static/fontello.1587147224637.css differ diff --git a/priv/static/static/js/app.5c94bdec79a7d0f3cfcb.js b/priv/static/static/js/app.5c94bdec79a7d0f3cfcb.js deleted file mode 100644 index 7ef7a5f12..000000000 Binary files a/priv/static/static/js/app.5c94bdec79a7d0f3cfcb.js and /dev/null differ diff --git a/priv/static/static/js/app.5c94bdec79a7d0f3cfcb.js.map b/priv/static/static/js/app.5c94bdec79a7d0f3cfcb.js.map deleted file mode 100644 index 163f78149..000000000 Binary files a/priv/static/static/js/app.5c94bdec79a7d0f3cfcb.js.map and /dev/null differ diff --git a/priv/static/static/js/app.def6476e8bc9b214218b.js b/priv/static/static/js/app.def6476e8bc9b214218b.js new file mode 100644 index 000000000..1e6ced42d Binary files /dev/null and b/priv/static/static/js/app.def6476e8bc9b214218b.js differ diff --git a/priv/static/static/js/app.def6476e8bc9b214218b.js.map b/priv/static/static/js/app.def6476e8bc9b214218b.js.map new file mode 100644 index 000000000..a03cad258 Binary files /dev/null and b/priv/static/static/js/app.def6476e8bc9b214218b.js.map differ diff --git a/priv/static/sw-pleroma.js b/priv/static/sw-pleroma.js index 88e8fcd5a..92361720e 100644 Binary files a/priv/static/sw-pleroma.js and b/priv/static/sw-pleroma.js differ diff --git a/test/notification_test.exs b/test/notification_test.exs index 837a9dacd..a7f53e319 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -8,11 +8,13 @@ defmodule Pleroma.NotificationTest do import Pleroma.Factory import Mock + alias Pleroma.FollowingRelationship alias Pleroma.Notification alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI.NotificationView alias Pleroma.Web.Push alias Pleroma.Web.Streamer @@ -272,16 +274,6 @@ test "it doesn't create a notification for user if he is the activity author" do refute Notification.create_notification(activity, author) end - test "it doesn't create a notification for follow-unfollow-follow chains" do - user = insert(:user) - followed_user = insert(:user) - {:ok, _, _, activity} = CommonAPI.follow(user, followed_user) - Notification.create_notification(activity, followed_user) - CommonAPI.unfollow(user, followed_user) - {:ok, _, _, activity_dupe} = CommonAPI.follow(user, followed_user) - refute Notification.create_notification(activity_dupe, followed_user) - end - test "it doesn't create duplicate notifications for follow+subscribed users" do user = insert(:user) subscriber = insert(:user) @@ -304,6 +296,74 @@ test "it doesn't create subscription notifications if the recipient cannot see t end end + describe "follow / follow_request notifications" do + test "it creates `follow` notification for approved Follow activity" do + user = insert(:user) + followed_user = insert(:user, locked: false) + + {:ok, _, _, _activity} = CommonAPI.follow(user, followed_user) + assert FollowingRelationship.following?(user, followed_user) + assert [notification] = Notification.for_user(followed_user) + + assert %{type: "follow"} = + NotificationView.render("show.json", %{ + notification: notification, + for: followed_user + }) + end + + test "if `follow_request` notifications are enabled, " <> + "it creates `follow_request` notification for pending Follow activity" do + clear_config([:notifications, :enable_follow_request_notifications], true) + user = insert(:user) + followed_user = insert(:user, locked: true) + + {:ok, _, _, _activity} = CommonAPI.follow(user, followed_user) + refute FollowingRelationship.following?(user, followed_user) + assert [notification] = Notification.for_user(followed_user) + + render_opts = %{notification: notification, for: followed_user} + assert %{type: "follow_request"} = NotificationView.render("show.json", render_opts) + + # After request is accepted, the same notification is rendered with type "follow": + assert {:ok, _} = CommonAPI.accept_follow_request(user, followed_user) + + notification_id = notification.id + assert [%{id: ^notification_id}] = Notification.for_user(followed_user) + assert %{type: "follow"} = NotificationView.render("show.json", render_opts) + end + + test "if `follow_request` notifications are disabled, " <> + "it does NOT create `follow*` notification for pending Follow activity" do + clear_config([:notifications, :enable_follow_request_notifications], false) + user = insert(:user) + followed_user = insert(:user, locked: true) + + {:ok, _, _, _activity} = CommonAPI.follow(user, followed_user) + refute FollowingRelationship.following?(user, followed_user) + assert [] = Notification.for_user(followed_user) + + # After request is accepted, no new notifications are generated: + assert {:ok, _} = CommonAPI.accept_follow_request(user, followed_user) + assert [] = Notification.for_user(followed_user) + end + + test "it doesn't create a notification for follow-unfollow-follow chains" do + user = insert(:user) + followed_user = insert(:user, locked: false) + + {:ok, _, _, _activity} = CommonAPI.follow(user, followed_user) + assert FollowingRelationship.following?(user, followed_user) + assert [notification] = Notification.for_user(followed_user) + + CommonAPI.unfollow(user, followed_user) + {:ok, _, _, _activity_dupe} = CommonAPI.follow(user, followed_user) + + notification_id = notification.id + assert [%{id: ^notification_id}] = Notification.for_user(followed_user) + end + end + describe "get notification" do test "it gets a notification that belongs to the user" do user = insert(:user) diff --git a/test/plugs/authentication_plug_test.exs b/test/plugs/authentication_plug_test.exs index ae2f3f8ec..646bda9d3 100644 --- a/test/plugs/authentication_plug_test.exs +++ b/test/plugs/authentication_plug_test.exs @@ -6,6 +6,8 @@ defmodule Pleroma.Plugs.AuthenticationPlugTest do use Pleroma.Web.ConnCase, async: true alias Pleroma.Plugs.AuthenticationPlug + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Plugs.PlugHelper alias Pleroma.User import ExUnit.CaptureLog @@ -36,13 +38,16 @@ test "it does nothing if a user is assigned", %{conn: conn} do assert ret_conn == conn end - test "with a correct password in the credentials, it assigns the auth_user", %{conn: conn} do + test "with a correct password in the credentials, " <> + "it assigns the auth_user and marks OAuthScopesPlug as skipped", + %{conn: conn} do conn = conn |> assign(:auth_credentials, %{password: "guy"}) |> AuthenticationPlug.call(%{}) assert conn.assigns.user == conn.assigns.auth_user + assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug) end test "with a wrong password in the credentials, it does nothing", %{conn: conn} do diff --git a/test/plugs/legacy_authentication_plug_test.exs b/test/plugs/legacy_authentication_plug_test.exs index 7559de7d3..3b8c07627 100644 --- a/test/plugs/legacy_authentication_plug_test.exs +++ b/test/plugs/legacy_authentication_plug_test.exs @@ -8,6 +8,8 @@ defmodule Pleroma.Plugs.LegacyAuthenticationPlugTest do import Pleroma.Factory alias Pleroma.Plugs.LegacyAuthenticationPlug + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Plugs.PlugHelper alias Pleroma.User setup do @@ -36,7 +38,8 @@ test "it does nothing if a user is assigned", %{conn: conn, user: user} do end @tag :skip_on_mac - test "it authenticates the auth_user if present and password is correct and resets the password", + test "if `auth_user` is present and password is correct, " <> + "it authenticates the user, resets the password, marks OAuthScopesPlug as skipped", %{ conn: conn, user: user @@ -49,6 +52,7 @@ test "it authenticates the auth_user if present and password is correct and rese conn = LegacyAuthenticationPlug.call(conn, %{}) assert conn.assigns.user.id == user.id + assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug) end @tag :skip_on_mac diff --git a/test/plugs/oauth_scopes_plug_test.exs b/test/plugs/oauth_scopes_plug_test.exs index abab7abb0..edbc94227 100644 --- a/test/plugs/oauth_scopes_plug_test.exs +++ b/test/plugs/oauth_scopes_plug_test.exs @@ -7,7 +7,6 @@ defmodule Pleroma.Plugs.OAuthScopesPlugTest do alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug alias Pleroma.Plugs.OAuthScopesPlug - alias Pleroma.Plugs.PlugHelper alias Pleroma.Repo import Mock @@ -21,7 +20,7 @@ test "is not performed if marked as skipped", %{conn: conn} do with_mock OAuthScopesPlug, [:passthrough], perform: &passthrough([&1, &2]) do conn = conn - |> PlugHelper.append_to_skipped_plugs(OAuthScopesPlug) + |> OAuthScopesPlug.skip_plug() |> OAuthScopesPlug.call(%{scopes: ["random_scope"]}) refute called(OAuthScopesPlug.perform(:_, :_)) diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index d3ad4866c..2889a577c 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -18,13 +18,14 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do describe "like objects" do setup do + poster = insert(:user) user = insert(:user) - {:ok, post} = CommonAPI.post(user, %{"status" => "hey"}) + {:ok, post} = CommonAPI.post(poster, %{"status" => "hey"}) {:ok, like_data, _meta} = Builder.like(user, post.object) {:ok, like, _meta} = ActivityPub.persist(like_data, local: true) - %{like: like, user: user} + %{like: like, user: user, poster: poster} end test "add the like to the original object", %{like: like, user: user} do @@ -33,6 +34,11 @@ test "add the like to the original object", %{like: like, user: user} do assert object.data["like_count"] == 1 assert user.ap_id in object.data["likes"] end + + test "creates a notification", %{like: like, poster: poster} do + {:ok, like, _} = SideEffects.handle(like) + assert Repo.get_by(Notification, user_id: poster.id, activity_id: like.id) + end end describe "creation of ChatMessages" do diff --git a/test/web/auth/basic_auth_test.exs b/test/web/auth/basic_auth_test.exs new file mode 100644 index 000000000..64f8a6863 --- /dev/null +++ b/test/web/auth/basic_auth_test.exs @@ -0,0 +1,46 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Auth.BasicAuthTest do + use Pleroma.Web.ConnCase + + import Pleroma.Factory + + test "with HTTP Basic Auth used, grants access to OAuth scope-restricted endpoints", %{ + conn: conn + } do + user = insert(:user) + assert Comeonin.Pbkdf2.checkpw("test", user.password_hash) + + basic_auth_contents = + (URI.encode_www_form(user.nickname) <> ":" <> URI.encode_www_form("test")) + |> Base.encode64() + + # Succeeds with HTTP Basic Auth + response = + conn + |> put_req_header("authorization", "Basic " <> basic_auth_contents) + |> get("/api/v1/accounts/verify_credentials") + |> json_response(200) + + user_nickname = user.nickname + assert %{"username" => ^user_nickname} = response + + # Succeeds with a properly scoped OAuth token + valid_token = insert(:oauth_token, scopes: ["read:accounts"]) + + conn + |> put_req_header("authorization", "Bearer #{valid_token.token}") + |> get("/api/v1/accounts/verify_credentials") + |> json_response(200) + + # Fails with a wrong-scoped OAuth token (proof of restriction) + invalid_token = insert(:oauth_token, scopes: ["read:something"]) + + conn + |> put_req_header("authorization", "Bearer #{invalid_token.token}") + |> get("/api/v1/accounts/verify_credentials") + |> json_response(403) + end +end