diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/pleroma/activity.ex | 15 | ||||
| -rw-r--r-- | lib/pleroma/plugs/oauth_plug.ex | 28 | ||||
| -rw-r--r-- | lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 69 | ||||
| -rw-r--r-- | lib/pleroma/web/mastodon_api/views/push_subscription_view.ex | 7 | ||||
| -rw-r--r-- | lib/pleroma/web/mastodon_api/views/status_view.ex | 23 | ||||
| -rw-r--r-- | lib/pleroma/web/push/push.ex | 120 | ||||
| -rw-r--r-- | lib/pleroma/web/push/subscription.ex | 14 | ||||
| -rw-r--r-- | lib/pleroma/web/twitter_api/controllers/util_controller.ex | 11 | 
8 files changed, 176 insertions, 111 deletions
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index c065f3b6c..200addd6e 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -3,6 +3,14 @@ defmodule Pleroma.Activity do    alias Pleroma.{Repo, Activity, Notification}    import Ecto.Query +  # https://github.com/tootsuite/mastodon/blob/master/app/models/notification.rb#L19 +  @mastodon_notification_types %{ +    "Create" => "mention", +    "Follow" => "follow", +    "Announce" => "reblog", +    "Like" => "favourite" +  } +    schema "activities" do      field(:data, :map)      field(:local, :boolean, default: true) @@ -88,4 +96,11 @@ defmodule Pleroma.Activity do    end    def get_in_reply_to_activity(_), do: nil + +  for {ap_type, type} <- @mastodon_notification_types do +    def mastodon_notification_type(%Activity{data: %{"type" => unquote(ap_type)}}), +      do: unquote(type) +  end + +  def mastodon_notification_type(%Activity{}), do: nil  end diff --git a/lib/pleroma/plugs/oauth_plug.ex b/lib/pleroma/plugs/oauth_plug.ex index 8b99a74d1..13c914c1b 100644 --- a/lib/pleroma/plugs/oauth_plug.ex +++ b/lib/pleroma/plugs/oauth_plug.ex @@ -15,10 +15,10 @@ defmodule Pleroma.Plugs.OAuthPlug do    def call(%{assigns: %{user: %User{}}} = conn, _), do: conn    def call(conn, _) do -    with {:ok, token} <- fetch_token(conn), -         {:ok, user} <- fetch_user(token) do +    with {:ok, token_str} <- fetch_token_str(conn), +         {:ok, user, token_record} <- fetch_user_and_token(token_str) do        conn -      |> assign(:token, token) +      |> assign(:token, token_record)        |> assign(:user, user)      else        _ -> conn @@ -27,12 +27,12 @@ defmodule Pleroma.Plugs.OAuthPlug do    # Gets user by token    # -  @spec fetch_user(String.t()) :: {:ok, User.t()} | nil -  defp fetch_user(token) do +  @spec fetch_user_and_token(String.t()) :: {:ok, User.t(), Token.t()} | nil +  defp fetch_user_and_token(token) do      query = from(q in Token, where: q.token == ^token, preload: [:user]) -    with %Token{user: %{info: %{deactivated: false} = _} = user} <- Repo.one(query) do -      {:ok, user} +    with %Token{user: %{info: %{deactivated: false} = _} = user} = token_record <- Repo.one(query) do +      {:ok, user, token_record}      end    end @@ -48,23 +48,23 @@ defmodule Pleroma.Plugs.OAuthPlug do    # Gets token from headers    # -  @spec fetch_token(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()} -  defp fetch_token(%Plug.Conn{} = conn) do +  @spec fetch_token_str(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()} +  defp fetch_token_str(%Plug.Conn{} = conn) do      headers = get_req_header(conn, "authorization") -    with :no_token_found <- fetch_token(headers), +    with :no_token_found <- fetch_token_str(headers),           do: fetch_token_from_session(conn)    end -  @spec fetch_token(Keyword.t()) :: :no_token_found | {:ok, String.t()} -  defp fetch_token([]), do: :no_token_found +  @spec fetch_token_str(Keyword.t()) :: :no_token_found | {:ok, String.t()} +  defp fetch_token_str([]), do: :no_token_found -  defp fetch_token([token | tail]) do +  defp fetch_token_str([token | tail]) do      trimmed_token = String.trim(token)      case Regex.run(@realm_reg, trimmed_token) do        [_, match] -> {:ok, String.trim(match)} -      _ -> fetch_token(tail) +      _ -> fetch_token_str(tail)      end    end  end diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 5c8602322..0414d73d8 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -1055,52 +1055,37 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    def render_notification(user, %{id: id, activity: activity, inserted_at: created_at} = _params) do      actor = User.get_cached_by_ap_id(activity.data["actor"]) +    parent_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"]) +    mastodon_type = Activity.mastodon_notification_type(activity) -    created_at = -      NaiveDateTime.to_iso8601(created_at) -      |> String.replace(~r/(\.\d+)?$/, ".000Z", global: false) - -    id = id |> to_string +    response = %{ +      id: to_string(id), +      type: mastodon_type, +      created_at: CommonAPI.Utils.to_masto_date(created_at), +      account: AccountView.render("account.json", %{user: actor, for: user}) +    } -    case activity.data["type"] do -      "Create" -> -        %{ -          id: id, -          type: "mention", -          created_at: created_at, -          account: AccountView.render("account.json", %{user: actor, for: user}), +    case mastodon_type do +      "mention" -> +        response +        |> Map.merge(%{            status: StatusView.render("status.json", %{activity: activity, for: user}) -        } +        }) -      "Like" -> -        liked_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"]) +      "favourite" -> +        response +        |> Map.merge(%{ +          status: StatusView.render("status.json", %{activity: parent_activity, for: user}) +        }) -        %{ -          id: id, -          type: "favourite", -          created_at: created_at, -          account: AccountView.render("account.json", %{user: actor, for: user}), -          status: StatusView.render("status.json", %{activity: liked_activity, for: user}) -        } +      "reblog" -> +        response +        |> Map.merge(%{ +          status: StatusView.render("status.json", %{activity: parent_activity, for: user}) +        }) -      "Announce" -> -        announced_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"]) - -        %{ -          id: id, -          type: "reblog", -          created_at: created_at, -          account: AccountView.render("account.json", %{user: actor, for: user}), -          status: StatusView.render("status.json", %{activity: announced_activity, for: user}) -        } - -      "Follow" -> -        %{ -          id: id, -          type: "follow", -          created_at: created_at, -          account: AccountView.render("account.json", %{user: actor, for: user}) -        } +      "follow" -> +        response        _ ->          nil @@ -1167,6 +1152,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def create_push_subscription(%{assigns: %{user: user, token: token}} = conn, params) do +    true = Pleroma.Web.Push.enabled()      Pleroma.Web.Push.Subscription.delete_if_exists(user, token)      {:ok, subscription} = Pleroma.Web.Push.Subscription.create(user, token, params)      view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) @@ -1174,6 +1160,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def get_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do +    true = Pleroma.Web.Push.enabled()      subscription = Pleroma.Web.Push.Subscription.get(user, token)      view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)      json(conn, view) @@ -1183,12 +1170,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do          %{assigns: %{user: user, token: token}} = conn,          params        ) do +    true = Pleroma.Web.Push.enabled()      {:ok, subscription} = Pleroma.Web.Push.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 = Pleroma.Web.Push.enabled()      {:ok, _response} = Pleroma.Web.Push.Subscription.delete(user, token)      json(conn, %{})    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 c8b95d14c..67e86294e 100644 --- a/lib/pleroma/web/mastodon_api/views/push_subscription_view.ex +++ b/lib/pleroma/web/mastodon_api/views/push_subscription_view.ex @@ -5,7 +5,12 @@ defmodule Pleroma.Web.MastodonAPI.PushSubscriptionView do      %{        id: to_string(subscription.id),        endpoint: subscription.endpoint, -      alerts: Map.get(subscription.data, "alerts") +      alerts: Map.get(subscription.data, "alerts"), +      server_key: server_key()      }    end + +  defp server_key do +    Keyword.get(Application.get_env(:web_push_encryption, :vapid_details), :public_key) +  end  end diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index c3c735d5d..46c559e3a 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -140,7 +140,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do        visibility: get_visibility(object),        media_attachments: attachments |> Enum.take(4),        mentions: mentions, -      tags: tags, +      tags: build_tags(tags),        application: %{          name: "Web",          website: nil @@ -235,6 +235,27 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do    def render_content(object), do: object["content"] || ""    @doc """ +  Builds a dictionary tags. + +  ## Examples + +  iex> Pleroma.Web.MastodonAPI.StatusView.build_tags(["fediverse", "nextcloud"]) +  [{"name": "fediverse", "url": "/tag/fediverse"}, +   {"name": "nextcloud", "url": "/tag/nextcloud"}] + +  """ +  @spec build_tags(list(any())) :: list(map()) +  def build_tags(object_tags) when is_list(object_tags) do +    object_tags = for tag when is_binary(tag) <- object_tags, do: tag + +    Enum.reduce(object_tags, [], fn tag, tags -> +      tags ++ [%{name: tag, url: "/tag/#{tag}"}] +    end) +  end + +  def build_tags(_), do: [] + +  @doc """    Builds list emojis.    Arguments: `nil` or list tuple of name and url. diff --git a/lib/pleroma/web/push/push.ex b/lib/pleroma/web/push/push.ex index 5a873ec19..477943450 100644 --- a/lib/pleroma/web/push/push.ex +++ b/lib/pleroma/web/push/push.ex @@ -9,67 +9,99 @@ defmodule Pleroma.Web.Push do    @types ["Create", "Follow", "Announce", "Like"] -  @gcm_api_key nil -    def start_link() do      GenServer.start_link(__MODULE__, :ok, name: __MODULE__)    end -  def init(:ok) do -    case Application.get_env(:web_push_encryption, :vapid_details) do -      nil -> -        Logger.warn( -          "VAPID key pair is not found. Please, add VAPID configuration to config. Run `mix web_push.gen.keypair` mix task to create a key pair" -        ) - -        :ignore +  def vapid_config() do +    Application.get_env(:web_push_encryption, :vapid_details, []) +  end -      _ -> -        {:ok, %{}} +  def enabled() do +    case vapid_config() do +      [] -> false +      list when is_list(list) -> true +      _ -> false      end    end    def send(notification) do -    if Application.get_env(:web_push_encryption, :vapid_details) do +    if enabled() do        GenServer.cast(Pleroma.Web.Push, {:send, notification})      end    end +  def init(:ok) do +    if !enabled() do +      Logger.warn(""" +      VAPID key pair is not found. If you wish to enabled web push, please run + +          mix web_push.gen.keypair + +      and add the resulting output to your configuration file. +      """) + +      :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"]) -    body = notification |> format(actor) |> Jason.encode!() + +    type = Pleroma.Activity.mastodon_notification_type(notification.activity)      Subscription      |> where(user_id: ^user_id) +    |> preload(:token)      |> Repo.all() -    |> Enum.each(fn record -> -      subscription = %{ +    |> Enum.filter(fn subscription -> +      get_in(subscription.data, ["alerts", type]) || false +    end) +    |> Enum.each(fn subscription -> +      sub = %{          keys: %{ -          p256dh: record.key_p256dh, -          auth: record.key_auth +          p256dh: subscription.key_p256dh, +          auth: subscription.key_auth          }, -        endpoint: record.endpoint +        endpoint: subscription.endpoint        } -      case WebPushEncryption.send_web_push(body, subscription, @gcm_api_key) do +      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!(record) +          Repo.delete!(subscription)            :ok          {:ok, %{status_code: code}} when 200 <= code and code < 300 ->            :ok          {:ok, %{status_code: code}} -> -          Logger.error("Web Push Nonification failed with code: #{code}") +          Logger.error("Web Push Notification failed with code: #{code}")            :error          _ -> -          Logger.error("Web Push Nonification failed with unknown error") +          Logger.error("Web Push Notification failed with unknown error")            :error        end      end) @@ -82,35 +114,21 @@ defmodule Pleroma.Web.Push do      {:noreply, state}    end -  def format(%{activity: %{data: %{"type" => "Create"}}}, actor) do -    %{ -      title: "New Mention", -      body: "@#{actor.nickname} has mentiond you", -      icon: User.avatar_url(actor) -    } -  end - -  def format(%{activity: %{data: %{"type" => "Follow"}}}, actor) do -    %{ -      title: "New Follower", -      body: "@#{actor.nickname} has followed you", -      icon: User.avatar_url(actor) -    } -  end - -  def format(%{activity: %{data: %{"type" => "Announce"}}}, actor) do -    %{ -      title: "New Announce", -      body: "@#{actor.nickname} has announced your post", -      icon: User.avatar_url(actor) -    } +  defp format_title(%{activity: %{data: %{"type" => type}}}) do +    case type do +      "Create" -> "New Mention" +      "Follow" -> "New Follower" +      "Announce" -> "New Repeat" +      "Like" -> "New Favorite" +    end    end -  def format(%{activity: %{data: %{"type" => "Like"}}}, actor) do -    %{ -      title: "New Like", -      body: "@#{actor.nickname} has liked your post", -      icon: User.avatar_url(actor) -    } +  defp format_body(%{activity: %{data: %{"type" => type}}}, actor) do +    case type do +      "Create" -> "@#{actor.nickname} has mentioned you" +      "Follow" -> "@#{actor.nickname} has followed you" +      "Announce" -> "@#{actor.nickname} has repeated your post" +      "Like" -> "@#{actor.nickname} has favorited your post" +    end    end  end diff --git a/lib/pleroma/web/push/subscription.ex b/lib/pleroma/web/push/subscription.ex index cfab7a98e..1ad405daf 100644 --- a/lib/pleroma/web/push/subscription.ex +++ b/lib/pleroma/web/push/subscription.ex @@ -37,8 +37,8 @@ defmodule Pleroma.Web.Push.Subscription do        user_id: user.id,        token_id: token.id,        endpoint: endpoint, -      key_auth: key_auth, -      key_p256dh: key_p256dh, +      key_auth: ensure_base64_urlsafe(key_auth), +      key_p256dh: ensure_base64_urlsafe(key_p256dh),        data: alerts(params)      })    end @@ -63,4 +63,14 @@ defmodule Pleroma.Web.Push.Subscription do        sub -> Repo.delete(sub)      end    end + +  # Some webpush clients (e.g. iOS Toot!) use an non urlsafe base64 as an encoding for the key. +  # However, the web push rfs specify to use base64 urlsafe, and the `web_push_encryption` library we use +  # requires the key to be properly encoded. So we just convert base64 to urlsafe base64. +  defp ensure_base64_urlsafe(string) do +    string +    |> String.replace("+", "-") +    |> String.replace("/", "_") +    |> String.replace("=", "") +  end  end diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index fb3b99d25..2f2b69623 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -156,14 +156,21 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do          |> send_resp(200, response)        _ -> -        vapid_public_key = -          Keyword.get(Application.get_env(:web_push_encryption, :vapid_details), :public_key) +        vapid_public_key = Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) + +        uploadlimit = %{ +          uploadlimit: to_string(Keyword.get(instance, :upload_limit)), +          avatarlimit: to_string(Keyword.get(instance, :avatar_upload_limit)), +          backgroundlimit: to_string(Keyword.get(instance, :background_upload_limit)), +          bannerlimit: to_string(Keyword.get(instance, :banner_upload_limit)) +        }          data = %{            name: Keyword.get(instance, :name),            description: Keyword.get(instance, :description),            server: Web.base_url(),            textlimit: to_string(Keyword.get(instance, :limit)), +          uploadlimit: uploadlimit,            closed: if(Keyword.get(instance, :registrations_open), do: "0", else: "1"),            private: if(Keyword.get(instance, :public, true), do: "0", else: "1"),            vapidPublicKey: vapid_public_key,  | 
