diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 39 | ||||
-rw-r--r-- | lib/pleroma/web/mastodon_api/subscription_controller.ex | 71 | ||||
-rw-r--r-- | lib/pleroma/web/mastodon_api/views/push_subscription_view.ex | 5 | ||||
-rw-r--r-- | lib/pleroma/web/push/impl.ex | 127 | ||||
-rw-r--r-- | lib/pleroma/web/push/push.ex | 135 | ||||
-rw-r--r-- | lib/pleroma/web/push/subscription.ex | 24 | ||||
-rw-r--r-- | lib/pleroma/web/router.ex | 8 |
7 files changed, 244 insertions, 165 deletions
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 4cd27c7a0..4fe66f856 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -15,14 +15,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Web alias Pleroma.Web.CommonAPI alias Pleroma.Web.MediaProxy - alias Pleroma.Web.Push - alias Push.Subscription alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.FilterView alias Pleroma.Web.MastodonAPI.ListView alias Pleroma.Web.MastodonAPI.MastodonView - alias Pleroma.Web.MastodonAPI.PushSubscriptionView alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.ReportView alias Pleroma.Web.ActivityPub.ActivityPub @@ -300,7 +297,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> Map.put(:visibility, "direct") activities = - ActivityPub.fetch_activities_query([user.ap_id], params) + [user.ap_id] + |> ActivityPub.fetch_activities_query(params) |> Repo.all() conn @@ -1419,37 +1417,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do json(conn, %{}) end - def create_push_subscription(%{assigns: %{user: user, token: token}} = conn, params) do - true = Push.enabled() - Subscription.delete_if_exists(user, token) - {:ok, subscription} = Subscription.create(user, token, params) - view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) - json(conn, view) - end - - def get_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do - true = Push.enabled() - subscription = Subscription.get(user, token) - view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) - json(conn, view) - end - - def update_push_subscription( - %{assigns: %{user: user, token: token}} = conn, - params - ) do - true = Push.enabled() - {:ok, subscription} = Subscription.update(user, token, params) - view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) - json(conn, view) - end - - def delete_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do - true = Push.enabled() - {:ok, _response} = Subscription.delete(user, token) - json(conn, %{}) - end - + # fallback action + # def errors(conn, _) do conn |> put_status(500) diff --git a/lib/pleroma/web/mastodon_api/subscription_controller.ex b/lib/pleroma/web/mastodon_api/subscription_controller.ex new file mode 100644 index 000000000..b6c8ff808 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/subscription_controller.ex @@ -0,0 +1,71 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.SubscriptionController do + @moduledoc "The module represents functions to manage user subscriptions." + use Pleroma.Web, :controller + + alias Pleroma.Web.Push + alias Pleroma.Web.Push.Subscription + alias Pleroma.Web.MastodonAPI.PushSubscriptionView, as: View + + action_fallback(:errors) + + # Creates PushSubscription + # POST /api/v1/push/subscription + # + def create(%{assigns: %{user: user, token: token}} = conn, params) do + with true <- Push.enabled(), + {:ok, _} <- Subscription.delete_if_exists(user, token), + {:ok, subscription} <- Subscription.create(user, token, params) do + view = View.render("push_subscription.json", subscription: subscription) + json(conn, view) + end + end + + # Gets PushSubscription + # GET /api/v1/push/subscription + # + def get(%{assigns: %{user: user, token: token}} = conn, _params) do + with true <- Push.enabled(), + {:ok, subscription} <- Subscription.get(user, token) do + view = View.render("push_subscription.json", subscription: subscription) + json(conn, view) + end + end + + # Updates PushSubscription + # PUT /api/v1/push/subscription + # + def update(%{assigns: %{user: user, token: token}} = conn, params) do + with true <- Push.enabled(), + {:ok, subscription} <- Subscription.update(user, token, params) do + view = View.render("push_subscription.json", subscription: subscription) + json(conn, view) + end + end + + # Deletes PushSubscription + # DELETE /api/v1/push/subscription + # + def delete(%{assigns: %{user: user, token: token}} = conn, _params) do + with true <- Push.enabled(), + {:ok, _response} <- Subscription.delete(user, token), + do: json(conn, %{}) + end + + # fallback action + # + def errors(conn, {:error, :not_found}) do + conn + |> put_status(404) + |> json("Not found") + end + + def errors(conn, _) do + conn + |> put_status(500) + |> json("Something went wrong") + end +end diff --git a/lib/pleroma/web/mastodon_api/views/push_subscription_view.ex b/lib/pleroma/web/mastodon_api/views/push_subscription_view.ex index e86b789c5..021489711 100644 --- a/lib/pleroma/web/mastodon_api/views/push_subscription_view.ex +++ b/lib/pleroma/web/mastodon_api/views/push_subscription_view.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.MastodonAPI.PushSubscriptionView do use Pleroma.Web, :view + alias Pleroma.Web.Push def render("push_subscription.json", %{subscription: subscription}) do %{ @@ -14,7 +15,5 @@ defmodule Pleroma.Web.MastodonAPI.PushSubscriptionView do } end - defp server_key do - Keyword.get(Application.get_env(:web_push_encryption, :vapid_details), :public_key) - end + defp server_key, do: Keyword.get(Push.vapid_config(), :public_key) end diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex new file mode 100644 index 000000000..33f912d34 --- /dev/null +++ b/lib/pleroma/web/push/impl.ex @@ -0,0 +1,127 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Push.Impl do + @moduledoc "The module represents implementation push web notification" + + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.Web.Push.Subscription + alias Pleroma.Web.Metadata.Utils + alias Pleroma.Notification + + require Logger + import Ecto.Query + + @types ["Create", "Follow", "Announce", "Like"] + + @doc "Performs sending notifications for user subscriptions" + @spec perform_send(Notification.t()) :: list(any) + def perform_send(%{activity: %{data: %{"type" => activity_type}}, user_id: user_id} = notif) + when activity_type in @types do + actor = User.get_cached_by_ap_id(notif.activity.data["actor"]) + + type = Activity.mastodon_notification_type(notif.activity) + gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key) + avatar_url = User.avatar_url(actor) + + for subscription <- fetch_subsriptions(user_id), + get_in(subscription.data, ["alerts", type]) do + %{ + title: format_title(notif), + access_token: subscription.token.token, + body: format_body(notif, actor), + notification_id: notif.id, + notification_type: type, + icon: avatar_url, + preferred_locale: "en" + } + |> Jason.encode!() + |> push_message(build_sub(subscription), gcm_api_key, subscription) + end + end + + def perform_send(_) do + Logger.warn("Unknown notification type") + :error + end + + @doc "Push message to web" + def push_message(body, sub, api_key, subscription) do + case WebPushEncryption.send_web_push(body, sub, api_key) do + {:ok, %{status_code: code}} when 400 <= code and code < 500 -> + Logger.debug("Removing subscription record") + Repo.delete!(subscription) + :ok + + {:ok, %{status_code: code}} when 200 <= code and code < 300 -> + :ok + + {:ok, %{status_code: code}} -> + Logger.error("Web Push Notification failed with code: #{code}") + :error + + _ -> + Logger.error("Web Push Notification failed with unknown error") + :error + end + end + + @doc "Gets user subscriptions" + def fetch_subsriptions(user_id) do + Subscription + |> where(user_id: ^user_id) + |> preload(:token) + |> Repo.all() + end + + def build_sub(subscription) do + %{ + keys: %{ + p256dh: subscription.key_p256dh, + auth: subscription.key_auth + }, + endpoint: subscription.endpoint + } + end + + def format_body( + %{activity: %{data: %{"type" => "Create", "object" => %{"content" => content}}}}, + actor + ) do + "@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}" + end + + def format_body( + %{activity: %{data: %{"type" => "Announce", "object" => activity_id}}}, + actor + ) do + %Activity{data: %{"object" => %{"id" => object_id}}} = Activity.get_by_ap_id(activity_id) + %Object{data: %{"content" => content}} = Object.get_by_ap_id(object_id) + + "@#{actor.nickname} repeated: #{Utils.scrub_html_and_truncate(content, 80)}" + end + + def format_body( + %{activity: %{data: %{"type" => type}}}, + actor + ) + when type in ["Follow", "Like"] do + case type do + "Follow" -> "@#{actor.nickname} has followed you" + "Like" -> "@#{actor.nickname} has favorited your post" + end + end + + def format_title(%{activity: %{data: %{"type" => type}}}) do + case type do + "Create" -> "New Mention" + "Follow" -> "New Follower" + "Announce" -> "New Repeat" + "Like" -> "New Favorite" + end + end +end diff --git a/lib/pleroma/web/push/push.ex b/lib/pleroma/web/push/push.ex index 92f8f9ab4..951dab535 100644 --- a/lib/pleroma/web/push/push.ex +++ b/lib/pleroma/web/push/push.ex @@ -5,17 +5,13 @@ defmodule Pleroma.Web.Push do use GenServer - alias Pleroma.Repo - alias Pleroma.User - alias Pleroma.Activity - alias Pleroma.Object - alias Pleroma.Web.Push.Subscription - alias Pleroma.Web.Metadata.Utils + alias Pleroma.Web.Push.Impl require Logger - import Ecto.Query - @types ["Create", "Follow", "Announce", "Like"] + ############## + # Client API # + ############## def start_link() do GenServer.start_link(__MODULE__, :ok, name: __MODULE__) @@ -33,14 +29,18 @@ defmodule Pleroma.Web.Push do end end - def send(notification) do - if enabled() do - GenServer.cast(Pleroma.Web.Push, {:send, notification}) - end - end + def send(notification), + do: GenServer.cast(__MODULE__, {:send, notification}) + + #################### + # Server Callbacks # + #################### + @impl true def init(:ok) do - if !enabled() do + if enabled() do + {:ok, nil} + else Logger.warn(""" VAPID key pair is not found. If you wish to enabled web push, please run @@ -50,112 +50,15 @@ defmodule Pleroma.Web.Push do """) :ignore - else - {:ok, nil} end end - def handle_cast( - {:send, %{activity: %{data: %{"type" => type}}, user_id: user_id} = notification}, - state - ) - when type in @types do - actor = User.get_cached_by_ap_id(notification.activity.data["actor"]) - - type = Pleroma.Activity.mastodon_notification_type(notification.activity) - - Subscription - |> where(user_id: ^user_id) - |> preload(:token) - |> Repo.all() - |> Enum.filter(fn subscription -> - get_in(subscription.data, ["alerts", type]) || false - end) - |> Enum.each(fn subscription -> - sub = %{ - keys: %{ - p256dh: subscription.key_p256dh, - auth: subscription.key_auth - }, - endpoint: subscription.endpoint - } - - body = - Jason.encode!(%{ - title: format_title(notification), - access_token: subscription.token.token, - body: format_body(notification, actor), - notification_id: notification.id, - notification_type: type, - icon: User.avatar_url(actor), - preferred_locale: "en" - }) - - case WebPushEncryption.send_web_push( - body, - sub, - Application.get_env(:web_push_encryption, :gcm_api_key) - ) do - {:ok, %{status_code: code}} when 400 <= code and code < 500 -> - Logger.debug("Removing subscription record") - Repo.delete!(subscription) - :ok - - {:ok, %{status_code: code}} when 200 <= code and code < 300 -> - :ok - - {:ok, %{status_code: code}} -> - Logger.error("Web Push Notification failed with code: #{code}") - :error - - _ -> - Logger.error("Web Push Notification failed with unknown error") - :error - end - end) - - {:noreply, state} - end - - def handle_cast({:send, _}, state) do - Logger.warn("Unknown notification type") - {:noreply, state} - end - - def format_body( - %{activity: %{data: %{"type" => "Create", "object" => %{"content" => content}}}}, - actor - ) do - "@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}" - end - - def format_body( - %{activity: %{data: %{"type" => "Announce", "object" => activity_id}}}, - actor - ) do - %Activity{data: %{"object" => %{"id" => object_id}}} = Activity.get_by_ap_id(activity_id) - %Object{data: %{"content" => content}} = Object.get_by_ap_id(object_id) - - "@#{actor.nickname} repeated: #{Utils.scrub_html_and_truncate(content, 80)}" - end - - def format_body( - %{activity: %{data: %{"type" => type}}}, - actor - ) - when type in ["Follow", "Like"] do - case type do - "Follow" -> "@#{actor.nickname} has followed you" - "Like" -> "@#{actor.nickname} has favorited your post" + @impl true + def handle_cast({:send, notification}, state) do + if enabled() do + Impl.perform_send(notification) end - end - defp format_title(%{activity: %{data: %{"type" => type}}}) do - case type do - "Create" -> "New Mention" - "Follow" -> "New Follower" - "Announce" -> "New Repeat" - "Like" -> "New Favorite" - end + {:noreply, state} end end diff --git a/lib/pleroma/web/push/subscription.ex b/lib/pleroma/web/push/subscription.ex index 242e30910..c90bd2bda 100644 --- a/lib/pleroma/web/push/subscription.ex +++ b/lib/pleroma/web/push/subscription.ex @@ -12,6 +12,8 @@ defmodule Pleroma.Web.Push.Subscription do alias Pleroma.Web.OAuth.Token alias Pleroma.Web.Push.Subscription + @type t :: %__MODULE__{} + schema "push_subscriptions" do belongs_to(:user, User, type: Pleroma.FlakeId) belongs_to(:token, Token) @@ -50,24 +52,32 @@ defmodule Pleroma.Web.Push.Subscription do }) end + @doc "Gets subsciption by user & token" + @spec get(User.t(), Token.t()) :: {:ok, t()} | {:error, :not_found} def get(%User{id: user_id}, %Token{id: token_id}) do - Repo.get_by(Subscription, user_id: user_id, token_id: token_id) + case Repo.get_by(Subscription, user_id: user_id, token_id: token_id) do + nil -> {:error, :not_found} + subscription -> {:ok, subscription} + end end def update(user, token, params) do - get(user, token) - |> change(data: alerts(params)) - |> Repo.update() + with {:ok, subscription} <- get(user, token) do + subscription + |> change(data: alerts(params)) + |> Repo.update() + end end def delete(user, token) do - Repo.delete(get(user, token)) + with {:ok, subscription} <- get(user, token), + do: Repo.delete(subscription) end def delete_if_exists(user, token) do case get(user, token) do - nil -> {:ok, nil} - sub -> Repo.delete(sub) + {:error, _} -> {:ok, nil} + {:ok, sub} -> Repo.delete(sub) end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 6fcb46878..fc322756a 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -304,10 +304,10 @@ defmodule Pleroma.Web.Router do scope [] do pipe_through(:oauth_push) - post("/push/subscription", MastodonAPIController, :create_push_subscription) - get("/push/subscription", MastodonAPIController, :get_push_subscription) - put("/push/subscription", MastodonAPIController, :update_push_subscription) - delete("/push/subscription", MastodonAPIController, :delete_push_subscription) + post("/push/subscription", SubscriptionController, :create) + get("/push/subscription", SubscriptionController, :get) + put("/push/subscription", SubscriptionController, :update) + delete("/push/subscription", SubscriptionController, :delete) end end |