diff options
| -rw-r--r-- | lib/pleroma/web/api_spec.ex | 7 | ||||
| -rw-r--r-- | lib/pleroma/web/api_spec/operations/subscription_operation.ex | 188 | ||||
| -rw-r--r-- | lib/pleroma/web/api_spec/schemas/push_subscription.ex | 66 | ||||
| -rw-r--r-- | lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex | 12 | ||||
| -rw-r--r-- | lib/pleroma/web/push/subscription.ex | 10 | ||||
| -rw-r--r-- | lib/pleroma/web/router.ex | 2 | ||||
| -rw-r--r-- | test/web/mastodon_api/controllers/subscription_controller_test.exs | 28 | 
7 files changed, 288 insertions, 25 deletions
diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex index b3c1e3ea2..79fd5f871 100644 --- a/lib/pleroma/web/api_spec.ex +++ b/lib/pleroma/web/api_spec.ex @@ -39,7 +39,12 @@ defmodule Pleroma.Web.ApiSpec do                password: %OpenApiSpex.OAuthFlow{                  authorizationUrl: "/oauth/authorize",                  tokenUrl: "/oauth/token", -                scopes: %{"read" => "read", "write" => "write", "follow" => "follow"} +                scopes: %{ +                  "read" => "read", +                  "write" => "write", +                  "follow" => "follow", +                  "push" => "push" +                }                }              }            } diff --git a/lib/pleroma/web/api_spec/operations/subscription_operation.ex b/lib/pleroma/web/api_spec/operations/subscription_operation.ex new file mode 100644 index 000000000..663b8fa11 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/subscription_operation.ex @@ -0,0 +1,188 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.SubscriptionOperation do +  alias OpenApiSpex.Operation +  alias OpenApiSpex.Schema +  alias Pleroma.Web.ApiSpec.Helpers +  alias Pleroma.Web.ApiSpec.Schemas.ApiError +  alias Pleroma.Web.ApiSpec.Schemas.PushSubscription + +  def open_api_operation(action) do +    operation = String.to_existing_atom("#{action}_operation") +    apply(__MODULE__, operation, []) +  end + +  def create_operation do +    %Operation{ +      tags: ["Push Subscriptions"], +      summary: "Subscribe to push notifications", +      description: +        "Add a Web Push API subscription to receive notifications. Each access token can have one push subscription. If you create a new subscription, the old subscription is deleted.", +      operationId: "SubscriptionController.create", +      security: [%{"oAuth" => ["push"]}], +      requestBody: Helpers.request_body("Parameters", create_request(), required: true), +      responses: %{ +        200 => Operation.response("Push Subscription", "application/json", PushSubscription), +        400 => Operation.response("Error", "application/json", ApiError), +        403 => Operation.response("Error", "application/json", ApiError) +      } +    } +  end + +  def show_operation do +    %Operation{ +      tags: ["Push Subscriptions"], +      summary: "Get current subscription", +      description: "View the PushSubscription currently associated with this access token.", +      operationId: "SubscriptionController.show", +      security: [%{"oAuth" => ["push"]}], +      responses: %{ +        200 => Operation.response("Push Subscription", "application/json", PushSubscription), +        403 => Operation.response("Error", "application/json", ApiError), +        404 => Operation.response("Error", "application/json", ApiError) +      } +    } +  end + +  def update_operation do +    %Operation{ +      tags: ["Push Subscriptions"], +      summary: "Change types of notifications", +      description: +        "Updates the current push subscription. Only the data part can be updated. To change fundamentals, a new subscription must be created instead.", +      operationId: "SubscriptionController.update", +      security: [%{"oAuth" => ["push"]}], +      requestBody: Helpers.request_body("Parameters", update_request(), required: true), +      responses: %{ +        200 => Operation.response("Push Subscription", "application/json", PushSubscription), +        403 => Operation.response("Error", "application/json", ApiError) +      } +    } +  end + +  def delete_operation do +    %Operation{ +      tags: ["Push Subscriptions"], +      summary: "Remove current subscription", +      description: "Removes the current Web Push API subscription.", +      operationId: "SubscriptionController.delete", +      security: [%{"oAuth" => ["push"]}], +      responses: %{ +        200 => Operation.response("Empty object", "application/json", %Schema{type: :object}), +        403 => Operation.response("Error", "application/json", ApiError), +        404 => Operation.response("Error", "application/json", ApiError) +      } +    } +  end + +  defp create_request do +    %Schema{ +      title: "SubscriptionCreateRequest", +      description: "POST body for creating a push subscription", +      type: :object, +      properties: %{ +        subscription: %Schema{ +          type: :object, +          properties: %{ +            endpoint: %Schema{ +              type: :string, +              description: "Endpoint URL that is called when a notification event occurs." +            }, +            keys: %Schema{ +              type: :object, +              properties: %{ +                p256dh: %Schema{ +                  type: :string, +                  description: +                    "User agent public key. Base64 encoded string of public key of ECDH key using `prime256v1` curve." +                }, +                auth: %Schema{ +                  type: :string, +                  description: "Auth secret. Base64 encoded string of 16 bytes of random data." +                } +              }, +              required: [:p256dh, :auth] +            } +          }, +          required: [:endpoint, :keys] +        }, +        data: %Schema{ +          type: :object, +          properties: %{ +            alerts: %Schema{ +              type: :object, +              properties: %{ +                follow: %Schema{type: :boolean, description: "Receive follow notifications?"}, +                favourite: %Schema{ +                  type: :boolean, +                  description: "Receive favourite notifications?" +                }, +                reblog: %Schema{type: :boolean, description: "Receive reblog notifications?"}, +                mention: %Schema{type: :boolean, description: "Receive mention notifications?"}, +                poll: %Schema{type: :boolean, description: "Receive poll notifications?"} +              } +            } +          } +        } +      }, +      required: [:subscription], +      example: %{ +        "subscription" => %{ +          "endpoint" => "https://example.com/example/1234", +          "keys" => %{ +            "auth" => "8eDyX_uCN0XRhSbY5hs7Hg==", +            "p256dh" => +              "BCIWgsnyXDv1VkhqL2P7YRBvdeuDnlwAPT2guNhdIoW3IP7GmHh1SMKPLxRf7x8vJy6ZFK3ol2ohgn_-0yP7QQA=" +          } +        }, +        "data" => %{ +          "alerts" => %{ +            "follow" => true, +            "mention" => true, +            "poll" => false +          } +        } +      } +    } +  end + +  defp update_request do +    %Schema{ +      title: "SubscriptionUpdateRequest", +      type: :object, +      properties: %{ +        data: %Schema{ +          type: :object, +          properties: %{ +            alerts: %Schema{ +              type: :object, +              properties: %{ +                follow: %Schema{type: :boolean, description: "Receive follow notifications?"}, +                favourite: %Schema{ +                  type: :boolean, +                  description: "Receive favourite notifications?" +                }, +                reblog: %Schema{type: :boolean, description: "Receive reblog notifications?"}, +                mention: %Schema{type: :boolean, description: "Receive mention notifications?"}, +                poll: %Schema{type: :boolean, description: "Receive poll notifications?"} +              } +            } +          } +        } +      }, +      example: %{ +        "data" => %{ +          "alerts" => %{ +            "follow" => true, +            "favourite" => true, +            "reblog" => true, +            "mention" => true, +            "poll" => true +          } +        } +      } +    } +  end +end diff --git a/lib/pleroma/web/api_spec/schemas/push_subscription.ex b/lib/pleroma/web/api_spec/schemas/push_subscription.ex new file mode 100644 index 000000000..cc91b95b8 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/push_subscription.ex @@ -0,0 +1,66 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.PushSubscription do +  alias OpenApiSpex.Schema + +  require OpenApiSpex + +  OpenApiSpex.schema(%{ +    title: "PushSubscription", +    description: "Response schema for a push subscription", +    type: :object, +    properties: %{ +      id: %Schema{ +        anyOf: [%Schema{type: :string}, %Schema{type: :integer}], +        description: "The id of the push subscription in the database." +      }, +      endpoint: %Schema{type: :string, description: "Where push alerts will be sent to."}, +      server_key: %Schema{type: :string, description: "The streaming server's VAPID key."}, +      alerts: %Schema{ +        type: :object, +        description: "Which alerts should be delivered to the endpoint.", +        properties: %{ +          follow: %Schema{ +            type: :boolean, +            description: "Receive a push notification when someone has followed you?" +          }, +          favourite: %Schema{ +            type: :boolean, +            description: +              "Receive a push notification when a status you created has been favourited by someone else?" +          }, +          reblog: %Schema{ +            type: :boolean, +            description: +              "Receive a push notification when a status you created has been boosted by someone else?" +          }, +          mention: %Schema{ +            type: :boolean, +            description: +              "Receive a push notification when someone else has mentioned you in a status?" +          }, +          poll: %Schema{ +            type: :boolean, +            description: +              "Receive a push notification when a poll you voted in or created has ended? " +          } +        } +      } +    }, +    example: %{ +      "id" => "328_183", +      "endpoint" => "https://yourdomain.example/listener", +      "alerts" => %{ +        "follow" => true, +        "favourite" => true, +        "reblog" => true, +        "mention" => true, +        "poll" => true +      }, +      "server_key" => +        "BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=" +    } +  }) +end diff --git a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex index d184ea1d0..34eac97c5 100644 --- a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex @@ -11,14 +11,16 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do    action_fallback(:errors) +  plug(Pleroma.Web.ApiSpec.CastAndValidate) +  plug(:restrict_push_enabled)    plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["push"]}) -  plug(:restrict_push_enabled) +  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.SubscriptionOperation    # Creates PushSubscription    # POST /api/v1/push/subscription    # -  def create(%{assigns: %{user: user, token: token}} = conn, params) do +  def create(%{assigns: %{user: user, token: token}, body_params: params} = conn, _) do      with {:ok, _} <- Subscription.delete_if_exists(user, token),           {:ok, subscription} <- Subscription.create(user, token, params) do        render(conn, "show.json", subscription: subscription) @@ -28,7 +30,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do    # Gets PushSubscription    # GET /api/v1/push/subscription    # -  def get(%{assigns: %{user: user, token: token}} = conn, _params) do +  def show(%{assigns: %{user: user, token: token}} = conn, _params) do      with {:ok, subscription} <- Subscription.get(user, token) do        render(conn, "show.json", subscription: subscription)      end @@ -37,7 +39,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do    # Updates PushSubscription    # PUT /api/v1/push/subscription    # -  def update(%{assigns: %{user: user, token: token}} = conn, params) do +  def update(%{assigns: %{user: user, token: token}, body_params: params} = conn, _) do      with {:ok, subscription} <- Subscription.update(user, token, params) do        render(conn, "show.json", subscription: subscription)      end @@ -66,7 +68,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do    def errors(conn, {:error, :not_found}) do      conn      |> put_status(:not_found) -    |> json(dgettext("errors", "Not found")) +    |> json(%{error: dgettext("errors", "Record not found")})    end    def errors(conn, _) do diff --git a/lib/pleroma/web/push/subscription.ex b/lib/pleroma/web/push/subscription.ex index b99b0c5fb..3e401a490 100644 --- a/lib/pleroma/web/push/subscription.ex +++ b/lib/pleroma/web/push/subscription.ex @@ -25,9 +25,9 @@ defmodule Pleroma.Web.Push.Subscription do      timestamps()    end -  @supported_alert_types ~w[follow favourite mention reblog] +  @supported_alert_types ~w[follow favourite mention reblog]a -  defp alerts(%{"data" => %{"alerts" => alerts}}) do +  defp alerts(%{data: %{alerts: alerts}}) do      alerts = Map.take(alerts, @supported_alert_types)      %{"alerts" => alerts}    end @@ -44,9 +44,9 @@ defmodule Pleroma.Web.Push.Subscription do          %User{} = user,          %Token{} = token,          %{ -          "subscription" => %{ -            "endpoint" => endpoint, -            "keys" => %{"auth" => key_auth, "p256dh" => key_p256dh} +          subscription: %{ +            endpoint: endpoint, +            keys: %{auth: key_auth, p256dh: key_p256dh}            }          } = params        ) do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index ef2239d59..281516bb8 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -427,7 +427,7 @@ defmodule Pleroma.Web.Router do      post("/statuses/:id/unmute", StatusController, :unmute_conversation)      post("/push/subscription", SubscriptionController, :create) -    get("/push/subscription", SubscriptionController, :get) +    get("/push/subscription", SubscriptionController, :show)      put("/push/subscription", SubscriptionController, :update)      delete("/push/subscription", SubscriptionController, :delete) diff --git a/test/web/mastodon_api/controllers/subscription_controller_test.exs b/test/web/mastodon_api/controllers/subscription_controller_test.exs index 5682498c0..4aa260663 100644 --- a/test/web/mastodon_api/controllers/subscription_controller_test.exs +++ b/test/web/mastodon_api/controllers/subscription_controller_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do    use Pleroma.Web.ConnCase    import Pleroma.Factory +    alias Pleroma.Web.Push    alias Pleroma.Web.Push.Subscription @@ -27,6 +28,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do        build_conn()        |> assign(:user, user)        |> assign(:token, token) +      |> put_req_header("content-type", "application/json")      %{conn: conn, user: user, token: token}    end @@ -47,8 +49,8 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do      test "returns error when push disabled ", %{conn: conn} do        assert_error_when_disable_push do          conn -        |> post("/api/v1/push/subscription", %{}) -        |> json_response(403) +        |> post("/api/v1/push/subscription", %{subscription: @sub}) +        |> json_response_and_validate_schema(403)        end      end @@ -59,7 +61,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do            "data" => %{"alerts" => %{"mention" => true, "test" => true}},            "subscription" => @sub          }) -        |> json_response(200) +        |> json_response_and_validate_schema(200)        [subscription] = Pleroma.Repo.all(Subscription) @@ -77,7 +79,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do        assert_error_when_disable_push do          conn          |> get("/api/v1/push/subscription", %{}) -        |> json_response(403) +        |> json_response_and_validate_schema(403)        end      end @@ -85,9 +87,9 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do        res =          conn          |> get("/api/v1/push/subscription", %{}) -        |> json_response(404) +        |> json_response_and_validate_schema(404) -      assert "Not found" == res +      assert %{"error" => "Record not found"} == res      end      test "returns a user subsciption", %{conn: conn, user: user, token: token} do @@ -101,7 +103,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do        res =          conn          |> get("/api/v1/push/subscription", %{}) -        |> json_response(200) +        |> json_response_and_validate_schema(200)        expect = %{          "alerts" => %{"mention" => true}, @@ -130,7 +132,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do        assert_error_when_disable_push do          conn          |> put("/api/v1/push/subscription", %{data: %{"alerts" => %{"mention" => false}}}) -        |> json_response(403) +        |> json_response_and_validate_schema(403)        end      end @@ -140,7 +142,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do          |> put("/api/v1/push/subscription", %{            data: %{"alerts" => %{"mention" => false, "follow" => true}}          }) -        |> json_response(200) +        |> json_response_and_validate_schema(200)        expect = %{          "alerts" => %{"follow" => true, "mention" => false}, @@ -158,7 +160,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do        assert_error_when_disable_push do          conn          |> delete("/api/v1/push/subscription", %{}) -        |> json_response(403) +        |> json_response_and_validate_schema(403)        end      end @@ -166,9 +168,9 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do        res =          conn          |> delete("/api/v1/push/subscription", %{}) -        |> json_response(404) +        |> json_response_and_validate_schema(404) -      assert "Not found" == res +      assert %{"error" => "Record not found"} == res      end      test "returns empty result and delete user subsciption", %{ @@ -186,7 +188,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do        res =          conn          |> delete("/api/v1/push/subscription", %{}) -        |> json_response(200) +        |> json_response_and_validate_schema(200)        assert %{} == res        refute Pleroma.Repo.get(Subscription, subscription.id)  | 
