Merge branch 'apps-api-endpoint' into 'develop'
authorAlex Gleason <alex@alexgleason.me>
Tue, 28 Dec 2021 14:38:02 +0000 (14:38 +0000)
committerAlex Gleason <alex@alexgleason.me>
Tue, 28 Dec 2021 14:38:02 +0000 (14:38 +0000)
Apps API endpoint

See merge request pleroma/pleroma!3583

13 files changed:
lib/pleroma/web/api_spec/operations/app_operation.ex
lib/pleroma/web/api_spec/operations/pleroma_app_operation.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/schemas/app.ex [new file with mode: 0644]
lib/pleroma/web/mastodon_api/controllers/app_controller.ex
lib/pleroma/web/o_auth/app.ex
lib/pleroma/web/pleroma_api/controllers/app_controller.ex [new file with mode: 0644]
lib/pleroma/web/pleroma_api/views/app_view.ex [new file with mode: 0644]
lib/pleroma/web/router.ex
priv/repo/migrations/20210818023112_add_user_id_to_apps.exs [new file with mode: 0644]
test/pleroma/web/mastodon_api/controllers/app_controller_test.exs
test/pleroma/web/o_auth/app_test.exs
test/pleroma/web/pleroma_api/controllers/app_controller_test.exs [new file with mode: 0644]
test/pleroma/web/pleroma_api/views/app_view_test.exs [new file with mode: 0644]

index dfb1c71706c4c4ed6b6be97f986f1c6af97aa81d..2284ac127658778ec9fce13d1d35b0b28f761da3 100644 (file)
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
   alias OpenApiSpex.Operation
   alias OpenApiSpex.Schema
   alias Pleroma.Web.ApiSpec.Helpers
+  alias Pleroma.Web.ApiSpec.Schemas.App
 
   @spec open_api_operation(atom) :: Operation.t()
   def open_api_operation(action) do
@@ -22,7 +23,7 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
       operationId: "AppController.create",
       requestBody: Helpers.request_body("Parameters", create_request(), required: true),
       responses: %{
-        200 => Operation.response("App", "application/json", create_response()),
+        200 => Operation.response("App", "application/json", App),
         422 =>
           Operation.response(
             "Unprocessable Entity",
@@ -119,30 +120,4 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
       }
     }
   end
-
-  defp create_response do
-    %Schema{
-      title: "AppCreateResponse",
-      description: "Response schema for an app",
-      type: :object,
-      properties: %{
-        id: %Schema{type: :string},
-        name: %Schema{type: :string},
-        client_id: %Schema{type: :string},
-        client_secret: %Schema{type: :string},
-        redirect_uri: %Schema{type: :string},
-        vapid_key: %Schema{type: :string},
-        website: %Schema{type: :string, nullable: true}
-      },
-      example: %{
-        "id" => "123",
-        "name" => "My App",
-        "client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
-        "client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
-        "vapid_key" =>
-          "BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
-        "website" => "https://myapp.com/"
-      }
-    }
-  end
 end
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_app_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_app_operation.ex
new file mode 100644 (file)
index 0000000..582a169
--- /dev/null
@@ -0,0 +1,31 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.PleromaAppOperation do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.App
+
+  def open_api_operation(action) do
+    operation = String.to_existing_atom("#{action}_operation")
+    apply(__MODULE__, operation, [])
+  end
+
+  @spec index_operation() :: Operation.t()
+  def index_operation do
+    %Operation{
+      tags: ["Applications"],
+      summary: "List applications",
+      description: "List the OAuth applications for the current user",
+      operationId: "AppController.index",
+      responses: %{
+        200 => Operation.response("Array of App", "application/json", array_of_apps())
+      }
+    }
+  end
+
+  defp array_of_apps do
+    %Schema{type: :array, items: App, example: [App.schema().example]}
+  end
+end
diff --git a/lib/pleroma/web/api_spec/schemas/app.ex b/lib/pleroma/web/api_spec/schemas/app.ex
new file mode 100644 (file)
index 0000000..c3d1af3
--- /dev/null
@@ -0,0 +1,33 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.App do
+  alias OpenApiSpex.Schema
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "App",
+    description: "Response schema for an app",
+    type: :object,
+    properties: %{
+      id: %Schema{type: :string},
+      name: %Schema{type: :string},
+      client_id: %Schema{type: :string},
+      client_secret: %Schema{type: :string},
+      redirect_uri: %Schema{type: :string},
+      vapid_key: %Schema{type: :string},
+      website: %Schema{type: :string, nullable: true}
+    },
+    example: %{
+      "id" => "123",
+      "name" => "My App",
+      "client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
+      "client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
+      "vapid_key" =>
+        "BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
+      "website" => "https://myapp.com/"
+    }
+  })
+end
index 93e63ba03826fdaa2f33a0608c95aa61b61b5aea..8d18140add66b39d3881ce081cb5ed5dfaae843b 100644 (file)
@@ -10,7 +10,9 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
 
   use Pleroma.Web, :controller
 
+  alias Pleroma.Maps
   alias Pleroma.Repo
+  alias Pleroma.User
   alias Pleroma.Web.OAuth.App
   alias Pleroma.Web.OAuth.Scopes
   alias Pleroma.Web.OAuth.Token
@@ -26,11 +28,13 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
   @doc "POST /api/v1/apps"
   def create(%{body_params: params} = conn, _params) do
     scopes = Scopes.fetch_scopes(params, ["read"])
+    user_id = get_user_id(conn)
 
     app_attrs =
       params
       |> Map.take([:client_name, :redirect_uris, :website])
       |> Map.put(:scopes, scopes)
+      |> Maps.put_if_present(:user_id, user_id)
 
     with cs <- App.register_changeset(%App{}, app_attrs),
          {:ok, app} <- Repo.insert(cs) do
@@ -38,6 +42,9 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
     end
   end
 
+  defp get_user_id(%{assigns: %{user: %User{id: user_id}}}), do: user_id
+  defp get_user_id(_conn), do: nil
+
   @doc """
   GET /api/v1/apps/verify_credentials
   Gets compact non-secret representation of the app. Supports app tokens and user tokens.
index 38275001036177a142cb0f2d436242bd2cc9063a..dacfbadc8ed174c881e5664a8b7de58f2bb89077 100644 (file)
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.OAuth.App do
   import Ecto.Changeset
   import Ecto.Query
   alias Pleroma.Repo
+  alias Pleroma.User
 
   @type t :: %__MODULE__{}
 
@@ -19,6 +20,8 @@ defmodule Pleroma.Web.OAuth.App do
     field(:client_secret, :string)
     field(:trusted, :boolean, default: false)
 
+    belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
+
     has_many(:oauth_authorizations, Pleroma.Web.OAuth.Authorization, on_delete: :delete_all)
     has_many(:oauth_tokens, Pleroma.Web.OAuth.Token, on_delete: :delete_all)
 
@@ -27,7 +30,7 @@ defmodule Pleroma.Web.OAuth.App do
 
   @spec changeset(t(), map()) :: Ecto.Changeset.t()
   def changeset(struct, params) do
-    cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted])
+    cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted, :user_id])
   end
 
   @spec register_changeset(t(), map()) :: Ecto.Changeset.t()
@@ -129,6 +132,12 @@ defmodule Pleroma.Web.OAuth.App do
     {:ok, Repo.all(query), count}
   end
 
+  @spec get_user_apps(User.t()) :: {:ok, [t()], non_neg_integer()}
+  def get_user_apps(%User{id: user_id}) do
+    from(a in __MODULE__, where: a.user_id == ^user_id)
+    |> Repo.all()
+  end
+
   @spec destroy(pos_integer()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
   def destroy(id) do
     with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do
diff --git a/lib/pleroma/web/pleroma_api/controllers/app_controller.ex b/lib/pleroma/web/pleroma_api/controllers/app_controller.ex
new file mode 100644 (file)
index 0000000..d857f42
--- /dev/null
@@ -0,0 +1,23 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.AppController do
+  use Pleroma.Web, :controller
+
+  alias Pleroma.Web.OAuth.App
+  alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+  plug(OAuthScopesPlug, %{scopes: ["follow", "read"]} when action in [:index])
+
+  plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAppOperation
+
+  @doc "GET /api/v1/pleroma/apps"
+  def index(%{assigns: %{user: user}} = conn, _params) do
+    with apps <- App.get_user_apps(user) do
+      render(conn, "index.json", %{apps: apps})
+    end
+  end
+end
diff --git a/lib/pleroma/web/pleroma_api/views/app_view.ex b/lib/pleroma/web/pleroma_api/views/app_view.ex
new file mode 100644 (file)
index 0000000..6b5d838
--- /dev/null
@@ -0,0 +1,11 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.AppView do
+  use Pleroma.Web, :view
+
+  def render("index.json", %{apps: apps}) do
+    render_many(apps, Pleroma.Web.MastodonAPI.AppView, "show.json")
+  end
+end
index 6defc80808d63aacdb63ea1b77e49ca9411df86b..e439a279d0ceb22e011d4fcc9d430bfc836b7f3e 100644 (file)
@@ -395,6 +395,7 @@ defmodule Pleroma.Web.Router do
   scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
     pipe_through(:api)
 
+    get("/apps", AppController, :index)
     get("/statuses/:id/reactions/:emoji", EmojiReactionController, :index)
     get("/statuses/:id/reactions", EmojiReactionController, :index)
   end
diff --git a/priv/repo/migrations/20210818023112_add_user_id_to_apps.exs b/priv/repo/migrations/20210818023112_add_user_id_to_apps.exs
new file mode 100644 (file)
index 0000000..88a6bce
--- /dev/null
@@ -0,0 +1,11 @@
+defmodule Pleroma.Repo.Migrations.AddUserIdToApps do
+  use Ecto.Migration
+
+  def change do
+    alter table(:apps) do
+      add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
+    end
+
+    create_if_not_exists(index(:apps, [:user_id]))
+  end
+end
index 76d81b942333274ae69d3c279375fa82c52aff88..bfbb7f32d676a4a2214a887f5c6e2c8eb1cca649 100644 (file)
@@ -35,6 +35,33 @@ defmodule Pleroma.Web.MastodonAPI.AppControllerTest do
   end
 
   test "creates an oauth app", %{conn: conn} do
+    app_attrs = build(:oauth_app)
+
+    conn =
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> post("/api/v1/apps", %{
+        client_name: app_attrs.client_name,
+        redirect_uris: app_attrs.redirect_uris
+      })
+
+    [app] = Repo.all(App)
+
+    expected = %{
+      "name" => app.client_name,
+      "website" => app.website,
+      "client_id" => app.client_id,
+      "client_secret" => app.client_secret,
+      "id" => app.id |> to_string(),
+      "redirect_uri" => app.redirect_uris,
+      "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
+    }
+
+    assert expected == json_response_and_validate_schema(conn, 200)
+    assert app.user_id == nil
+  end
+
+  test "creates an oauth app with a user", %{conn: conn} do
     user = insert(:user)
     app_attrs = build(:oauth_app)
 
@@ -60,5 +87,6 @@ defmodule Pleroma.Web.MastodonAPI.AppControllerTest do
     }
 
     assert expected == json_response_and_validate_schema(conn, 200)
+    assert app.user_id == user.id
   end
 end
index fc2f0d94080812820d1a3102a73f327856ffbbd8..a5223b0a53268d31c1b1213602767c9c0c33afd1 100644 (file)
@@ -41,4 +41,16 @@ defmodule Pleroma.Web.OAuth.AppTest do
       assert error.type == :unique
     end
   end
+
+  test "get_user_apps/1" do
+    user = insert(:user)
+
+    apps = [
+      insert(:oauth_app, user_id: user.id),
+      insert(:oauth_app, user_id: user.id),
+      insert(:oauth_app, user_id: user.id)
+    ]
+
+    assert App.get_user_apps(user) == apps
+  end
 end
diff --git a/test/pleroma/web/pleroma_api/controllers/app_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/app_controller_test.exs
new file mode 100644 (file)
index 0000000..5e24e18
--- /dev/null
@@ -0,0 +1,53 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.AppControllerTest do
+  use Pleroma.Web.ConnCase, async: true
+
+  alias Pleroma.Web.OAuth.App
+  alias Pleroma.Web.Push
+
+  import Pleroma.Factory
+
+  test "apps", %{conn: conn} do
+    user = insert(:user)
+    app_attrs = build(:oauth_app)
+
+    creation =
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> assign(:user, user)
+      |> post("/api/v1/apps", %{
+        client_name: app_attrs.client_name,
+        redirect_uris: app_attrs.redirect_uris
+      })
+
+    [app] = App.get_user_apps(user)
+
+    expected = %{
+      "name" => app.client_name,
+      "website" => app.website,
+      "client_id" => app.client_id,
+      "client_secret" => app.client_secret,
+      "id" => app.id |> to_string(),
+      "redirect_uri" => app.redirect_uris,
+      "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
+    }
+
+    assert expected == json_response_and_validate_schema(creation, 200)
+
+    response =
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> assign(:user, user)
+      |> assign(:token, insert(:oauth_token, user: user, scopes: ["read", "follow"]))
+      |> get("/api/v1/pleroma/apps")
+      |> json_response_and_validate_schema(200)
+
+    [apps] = response
+
+    assert length(response) == 1
+    assert apps["client_id"] == app.client_id
+  end
+end
diff --git a/test/pleroma/web/pleroma_api/views/app_view_test.exs b/test/pleroma/web/pleroma_api/views/app_view_test.exs
new file mode 100644 (file)
index 0000000..f0aee69
--- /dev/null
@@ -0,0 +1,21 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.AppViewTest do
+  use Pleroma.DataCase, async: true
+  alias Pleroma.Web.PleromaAPI.AppView
+  import Pleroma.Factory
+
+  test "index.json" do
+    apps = [
+      insert(:oauth_app),
+      insert(:oauth_app),
+      insert(:oauth_app)
+    ]
+
+    results = AppView.render("index.json", %{apps: apps})
+
+    assert [%{client_id: _, client_secret: _}, _, _] = results
+  end
+end