creating trusted app from adminFE & mix task
authorAlexander Strizhakov <alex.strizhakov@gmail.com>
Fri, 28 Feb 2020 08:16:40 +0000 (11:16 +0300)
committerAlexander Strizhakov <alex.strizhakov@gmail.com>
Mon, 23 Mar 2020 07:44:47 +0000 (10:44 +0300)
15 files changed:
CHANGELOG.md
docs/API/admin_api.md
docs/administration/CLI_tasks/oauth_app.md [new file with mode: 0644]
lib/mix/tasks/pleroma/app.ex [new file with mode: 0644]
lib/pleroma/web/admin_api/admin_api_controller.ex
lib/pleroma/web/mastodon_api/controllers/account_controller.ex
lib/pleroma/web/mastodon_api/views/app_view.ex
lib/pleroma/web/oauth/app.ex
lib/pleroma/web/router.ex
lib/pleroma/web/twitter_api/twitter_api.ex
priv/repo/migrations/20200227122417_add_trusted_to_apps.exs [new file with mode: 0644]
test/support/factory.ex
test/tasks/app_test.exs [new file with mode: 0644]
test/web/admin_api/admin_api_controller_test.exs
test/web/mastodon_api/controllers/account_controller_test.exs

index 15a073c6488eb714d26ef4f6c25b706e7f7344e8..a1271cbcaaa23851d22e96831b152025e27a31bc 100644 (file)
@@ -99,6 +99,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Add an option `authorized_fetch_mode` to require HTTP signatures for AP fetches.
 - ActivityPub: support for `replies` collection (output for outgoing federation & fetching on incoming federation).
 - Mix task to refresh counter cache (`mix pleroma.refresh_counter_cache`)
+- Mix task to create trusted OAuth App.
 <details>
   <summary>API Changes</summary>
 
@@ -145,6 +146,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - ActivityPub: `[:activitypub, :note_replies_output_limit]` setting sets the number of note self-replies to output on outgoing federation.
 - Admin API: `GET /api/pleroma/admin/stats` to get status count by visibility scope
 - Admin API: `GET /api/pleroma/admin/statuses` - list all statuses (accepts `godmode` and `local_only`)
+- Admin API: endpoints for create/update/delete OAuth Apps.
 </details>
 
 ### Fixed
index 47afdfba5a576d6678c677194a6a34b7b268b44d..4d12698ec6a9113edf5b35577a75cf7e95441363 100644 (file)
@@ -983,3 +983,104 @@ Loads json generated from `config/descriptions.exs`.
   }
 }
 ```
+
+## `GET /api/pleroma/admin/oauth_app`
+
+### List OAuth app
+
+- Params:
+  - *optional* `name`
+  - *optional* `client_id`
+  - *optional* `page`
+  - *optional* `page_size`
+  - *optional* `trusted`
+
+- Response:
+
+```json
+{
+  "apps": [
+    {
+      "id": 1,
+      "name": "App name",
+      "client_id": "yHoDSiWYp5mPV6AfsaVOWjdOyt5PhWRiafi6MRd1lSk",
+      "client_secret": "nLmis486Vqrv2o65eM9mLQx_m_4gH-Q6PcDpGIMl6FY",
+      "redirect_uri": "https://example.com/oauth-callback",
+      "website": "https://example.com",
+      "trusted": true
+    }
+  ],
+  "count": 17,
+  "page_size": 50
+}
+```
+
+
+## `POST /api/pleroma/admin/oauth_app`
+
+### Create OAuth App
+
+- Params:
+  - `name`
+  - `redirect_uris`
+  - `scopes`
+  - *optional* `website`
+  - *optional* `trusted`
+
+- Response:
+
+```json
+{
+  "id": 1,
+  "name": "App name",
+  "client_id": "yHoDSiWYp5mPV6AfsaVOWjdOyt5PhWRiafi6MRd1lSk",
+  "client_secret": "nLmis486Vqrv2o65eM9mLQx_m_4gH-Q6PcDpGIMl6FY",
+  "redirect_uri": "https://example.com/oauth-callback",
+  "website": "https://example.com",
+  "trusted": true
+}
+```
+
+- On failure:
+```json
+{
+  "redirect_uris": "can't be blank",
+  "name": "can't be blank"
+}
+```
+
+## `PATCH /api/pleroma/admin/oauth_app/:id`
+
+### Update OAuth App
+
+- Params:
+  -  *optional* `name`
+  -  *optional* `redirect_uris`
+  -  *optional* `scopes`
+  -  *optional* `website`
+  -  *optional* `trusted`
+
+- Response:
+
+```json
+{
+  "id": 1,
+  "name": "App name",
+  "client_id": "yHoDSiWYp5mPV6AfsaVOWjdOyt5PhWRiafi6MRd1lSk",
+  "client_secret": "nLmis486Vqrv2o65eM9mLQx_m_4gH-Q6PcDpGIMl6FY",
+  "redirect_uri": "https://example.com/oauth-callback",
+  "website": "https://example.com",
+  "trusted": true
+}
+```
+
+## `DELETE /api/pleroma/admin/oauth_app/:id`
+
+### Delete OAuth App
+
+- Params: None
+
+- Response:
+  - On success: `204`, empty response
+  - On failure:
+    - 400 Bad Request `"Invalid parameters"` when `status` is missing
\ No newline at end of file
diff --git a/docs/administration/CLI_tasks/oauth_app.md b/docs/administration/CLI_tasks/oauth_app.md
new file mode 100644 (file)
index 0000000..4d6bfc2
--- /dev/null
@@ -0,0 +1,16 @@
+# Creating trusted OAuth App
+
+{! backend/administration/CLI_tasks/general_cli_task_info.include !}
+
+## Create trusted OAuth App.
+
+Optional params:
+  * `-s SCOPES` - scopes for app, e.g. `read,write,follow,push`.
+
+```sh tab="OTP"
+ ./bin/pleroma_ctl app create -n APP_NAME -r REDIRECT_URI
+```
+
+```sh tab="From Source"
+mix pleroma.app create -n APP_NAME -r REDIRECT_URI
+```
\ No newline at end of file
diff --git a/lib/mix/tasks/pleroma/app.ex b/lib/mix/tasks/pleroma/app.ex
new file mode 100644 (file)
index 0000000..463e244
--- /dev/null
@@ -0,0 +1,49 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Tasks.Pleroma.App do
+  @moduledoc File.read!("docs/administration/CLI_tasks/oauth_app.md")
+  use Mix.Task
+
+  import Mix.Pleroma
+
+  @shortdoc "Creates trusted OAuth App"
+
+  def run(["create" | options]) do
+    start_pleroma()
+
+    {opts, _} =
+      OptionParser.parse!(options,
+        strict: [name: :string, redirect_uri: :string, scopes: :string],
+        aliases: [n: :name, r: :redirect_uri, s: :scopes]
+      )
+
+    scopes =
+      if opts[:scopes] do
+        String.split(opts[:scopes], ",")
+      else
+        ["read", "write", "follow", "push"]
+      end
+
+    params = %{
+      client_name: opts[:name],
+      redirect_uris: opts[:redirect_uri],
+      trusted: true,
+      scopes: scopes
+    }
+
+    with {:ok, app} <- Pleroma.Web.OAuth.App.create(params) do
+      shell_info("#{app.client_name} successfully created:")
+      shell_info("App client_id: " <> app.client_id)
+      shell_info("App client_secret: " <> app.client_secret)
+    else
+      {:error, changeset} ->
+        shell_error("Creating failed:")
+
+        Enum.each(Pleroma.Web.OAuth.App.errors(changeset), fn {key, error} ->
+          shell_error("#{key}: #{error}")
+        end)
+    end
+  end
+end
index 175260bc2bcb8069aaa115722de136c975caef4d..b03fa7169ff86e7e202d272c7f37dc9f21475d1a 100644 (file)
@@ -27,7 +27,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
   alias Pleroma.Web.AdminAPI.Search
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.Endpoint
+  alias Pleroma.Web.MastodonAPI.AppView
   alias Pleroma.Web.MastodonAPI.StatusView
+  alias Pleroma.Web.OAuth.App
   alias Pleroma.Web.Router
 
   require Logger
@@ -978,6 +980,83 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     conn |> json("")
   end
 
+  def oauth_app_create(conn, params) do
+    params =
+      if params["name"] do
+        Map.put(params, "client_name", params["name"])
+      else
+        params
+      end
+
+    result =
+      case App.create(params) do
+        {:ok, app} ->
+          AppView.render("show.json", %{app: app, admin: true})
+
+        {:error, changeset} ->
+          App.errors(changeset)
+      end
+
+    json(conn, result)
+  end
+
+  def oauth_app_update(conn, params) do
+    params =
+      if params["name"] do
+        Map.put(params, "client_name", params["name"])
+      else
+        params
+      end
+
+    with {:ok, app} <- App.update(params) do
+      json(conn, AppView.render("show.json", %{app: app, admin: true}))
+    else
+      {:error, changeset} ->
+        json(conn, App.errors(changeset))
+
+      nil ->
+        json_response(conn, :bad_request, "")
+    end
+  end
+
+  def oauth_app_list(conn, params) do
+    {page, page_size} = page_params(params)
+
+    search_params = %{
+      client_name: params["name"],
+      client_id: params["client_id"],
+      page: page,
+      page_size: page_size
+    }
+
+    search_params =
+      if Map.has_key?(params, "trusted") do
+        Map.put(search_params, :trusted, params["trusted"])
+      else
+        search_params
+      end
+
+    with {:ok, apps, count} <- App.search(search_params) do
+      json(
+        conn,
+        AppView.render("index.json",
+          apps: apps,
+          count: count,
+          page_size: page_size,
+          admin: true
+        )
+      )
+    end
+  end
+
+  def oauth_app_delete(conn, params) do
+    with {:ok, _app} <- App.destroy(params["id"]) do
+      json_response(conn, :no_content, "")
+    else
+      _ -> json_response(conn, :bad_request, "")
+    end
+  end
+
   def stats(conn, _) do
     count = Stats.get_status_visibility_count()
 
index 6dbf11ac92ff080f470d7df30277ff52f0a945fc..5f8aa2e3e6c297e8cb64eabeb9dde994e646616c 100644 (file)
@@ -92,6 +92,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
       |> Map.put("fullname", params["fullname"] || nickname)
       |> Map.put("bio", params["bio"] || "")
       |> Map.put("confirm", params["password"])
+      |> Map.put("trusted_app", app.trusted)
 
     with :ok <- validate_email_param(params),
          {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
index d934e21072ffafe780ad0d3fc7495cf670c5ba2d..36071cd25dbaa70672bc6cec69335923f16769a7 100644 (file)
@@ -7,6 +7,21 @@ defmodule Pleroma.Web.MastodonAPI.AppView do
 
   alias Pleroma.Web.OAuth.App
 
+  def render("index.json", %{apps: apps, count: count, page_size: page_size, admin: true}) do
+    %{
+      apps: render_many(apps, Pleroma.Web.MastodonAPI.AppView, "show.json", %{admin: true}),
+      count: count,
+      page_size: page_size
+    }
+  end
+
+  def render("show.json", %{admin: true, app: %App{} = app} = assigns) do
+    "show.json"
+    |> render(Map.delete(assigns, :admin))
+    |> Map.put(:trusted, app.trusted)
+    |> Map.put(:id, app.id)
+  end
+
   def render("show.json", %{app: %App{} = app}) do
     %{
       id: app.id |> to_string,
index 01ed326f4b7a8ca2a902b824b813a3670fb2118c..6a6d5f2e2a40a1dce242af3c3e811e0ac23f8136 100644 (file)
@@ -5,6 +5,7 @@
 defmodule Pleroma.Web.OAuth.App do
   use Ecto.Schema
   import Ecto.Changeset
+  import Ecto.Query
   alias Pleroma.Repo
 
   @type t :: %__MODULE__{}
@@ -16,14 +17,24 @@ defmodule Pleroma.Web.OAuth.App do
     field(:website, :string)
     field(:client_id, :string)
     field(:client_secret, :string)
+    field(:trusted, :boolean, default: false)
+
+    has_many(:oauth_authorizations, Pleroma.Web.OAuth.Authorization, on_delete: :delete_all)
+    has_many(:oauth_tokens, Pleroma.Web.OAuth.Token, on_delete: :delete_all)
 
     timestamps()
   end
 
+  @spec changeset(App.t(), map()) :: Ecto.Changeset.t()
+  def changeset(struct, params) do
+    cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted])
+  end
+
+  @spec register_changeset(App.t(), map()) :: Ecto.Changeset.t()
   def register_changeset(struct, params \\ %{}) do
     changeset =
       struct
-      |> cast(params, [:client_name, :redirect_uris, :scopes, :website])
+      |> changeset(params)
       |> validate_required([:client_name, :redirect_uris, :scopes])
 
     if changeset.valid? do
@@ -41,6 +52,21 @@ defmodule Pleroma.Web.OAuth.App do
     end
   end
 
+  @spec create(map()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
+  def create(params) do
+    with changeset <- __MODULE__.register_changeset(%__MODULE__{}, params) do
+      Repo.insert(changeset)
+    end
+  end
+
+  @spec update(map()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
+  def update(params) do
+    with %__MODULE__{} = app <- Repo.get(__MODULE__, params["id"]),
+         changeset <- changeset(app, params) do
+      Repo.update(changeset)
+    end
+  end
+
   @doc """
   Gets app by attrs or create new  with attrs.
   And updates the scopes if need.
@@ -65,4 +91,58 @@ defmodule Pleroma.Web.OAuth.App do
     |> change(%{scopes: scopes})
     |> Repo.update()
   end
+
+  @spec search(map()) :: {:ok, [App.t()], non_neg_integer()}
+  def search(params) do
+    query = from(a in __MODULE__)
+
+    query =
+      if params[:client_name] do
+        from(a in query, where: a.client_name == ^params[:client_name])
+      else
+        query
+      end
+
+    query =
+      if params[:client_id] do
+        from(a in query, where: a.client_id == ^params[:client_id])
+      else
+        query
+      end
+
+    query =
+      if Map.has_key?(params, :trusted) do
+        from(a in query, where: a.trusted == ^params[:trusted])
+      else
+        query
+      end
+
+    query =
+      from(u in query,
+        limit: ^params[:page_size],
+        offset: ^((params[:page] - 1) * params[:page_size])
+      )
+
+    count = Repo.aggregate(__MODULE__, :count, :id)
+
+    {:ok, Repo.all(query), count}
+  end
+
+  @spec destroy(pos_integer()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
+  def destroy(id) do
+    with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do
+      Repo.delete(app)
+    end
+  end
+
+  @spec errors(Ecto.Changeset.t()) :: map()
+  def errors(changeset) do
+    Enum.reduce(changeset.errors, %{}, fn
+      {:client_name, {error, _}}, acc ->
+        Map.put(acc, :name, error)
+
+      {key, {error, _}}, acc ->
+        Map.put(acc, key, error)
+    end)
+  end
 end
index 3f36f6c1a8d000b7e3f7de3f554a77dd02a2888d..c37ef59a00f2c4c7f6a3e93629d915b9f100cdc8 100644 (file)
@@ -203,6 +203,11 @@ defmodule Pleroma.Web.Router do
 
     post("/reload_emoji", AdminAPIController, :reload_emoji)
     get("/stats", AdminAPIController, :stats)
+
+    get("/oauth_app", AdminAPIController, :oauth_app_list)
+    post("/oauth_app", AdminAPIController, :oauth_app_create)
+    patch("/oauth_app/:id", AdminAPIController, :oauth_app_update)
+    delete("/oauth_app/:id", AdminAPIController, :oauth_app_delete)
   end
 
   scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
index f9c0994da0761457689e038f775576a15f535ed5..7a1ba6936e3758fe1ebfc259b0f97199a361adc9 100644 (file)
@@ -13,6 +13,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
 
   def register_user(params, opts \\ []) do
     token = params["token"]
+    trusted_app? = params["trusted_app"]
 
     params = %{
       nickname: params["nickname"],
@@ -29,7 +30,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
     captcha_enabled = Pleroma.Config.get([Pleroma.Captcha, :enabled])
     # true if captcha is disabled or enabled and valid, false otherwise
     captcha_ok =
-      if not captcha_enabled do
+      if trusted_app? || not captcha_enabled do
         :ok
       else
         Pleroma.Captcha.validate(
diff --git a/priv/repo/migrations/20200227122417_add_trusted_to_apps.exs b/priv/repo/migrations/20200227122417_add_trusted_to_apps.exs
new file mode 100644 (file)
index 0000000..4e2a62a
--- /dev/null
@@ -0,0 +1,9 @@
+defmodule Pleroma.Repo.Migrations.AddTrustedToApps do
+  use Ecto.Migration
+
+  def change do
+    alter table(:apps) do
+      add(:trusted, :boolean, default: false)
+    end
+  end
+end
index af639b6cd0b5f09af452e6c66769e7bfb7063d1a..f0b797fd4411b781e6825ebd34e4a4878b55a7af 100644 (file)
@@ -294,7 +294,7 @@ defmodule Pleroma.Factory do
 
   def oauth_app_factory do
     %Pleroma.Web.OAuth.App{
-      client_name: "Some client",
+      client_name: sequence(:client_name, &"Some client #{&1}"),
       redirect_uris: "https://example.com/callback",
       scopes: ["read", "write", "follow", "push", "admin"],
       website: "https://example.com",
diff --git a/test/tasks/app_test.exs b/test/tasks/app_test.exs
new file mode 100644 (file)
index 0000000..b8f0356
--- /dev/null
@@ -0,0 +1,65 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Tasks.Pleroma.AppTest do
+  use Pleroma.DataCase, async: true
+
+  setup_all do
+    Mix.shell(Mix.Shell.Process)
+
+    on_exit(fn ->
+      Mix.shell(Mix.Shell.IO)
+    end)
+  end
+
+  describe "creates new app" do
+    test "with default scopes" do
+      name = "Some name"
+      redirect = "https://example.com"
+      Mix.Tasks.Pleroma.App.run(["create", "-n", name, "-r", redirect])
+
+      assert_app(name, redirect, ["read", "write", "follow", "push"])
+    end
+
+    test "with custom scopes" do
+      name = "Another name"
+      redirect = "https://example.com"
+
+      Mix.Tasks.Pleroma.App.run([
+        "create",
+        "-n",
+        name,
+        "-r",
+        redirect,
+        "-s",
+        "read,write,follow,push,admin"
+      ])
+
+      assert_app(name, redirect, ["read", "write", "follow", "push", "admin"])
+    end
+  end
+
+  test "with errors" do
+    Mix.Tasks.Pleroma.App.run(["create"])
+    {:mix_shell, :error, ["Creating failed:"]}
+    {:mix_shell, :error, ["name: can't be blank"]}
+    {:mix_shell, :error, ["redirect_uris: can't be blank"]}
+  end
+
+  defp assert_app(name, redirect, scopes) do
+    app = Repo.get_by(Pleroma.Web.OAuth.App, client_name: name)
+
+    assert_received {:mix_shell, :info, [message]}
+    assert message == "#{name} successfully created:"
+
+    assert_received {:mix_shell, :info, [message]}
+    assert message == "App client_id: #{app.client_id}"
+
+    assert_received {:mix_shell, :info, [message]}
+    assert message == "App client_secret: #{app.client_secret}"
+
+    assert app.scopes == scopes
+    assert app.redirect_uris == redirect
+  end
+end
index 0a902585d367eeb6b187b7d1dcd2827f7c8b664b..d77e8d1d22ea9daf7c263c7831840424f7ea6a93 100644 (file)
@@ -3623,6 +3623,191 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                response["status_visibility"]
     end
   end
+
+  describe "POST /api/pleroma/admin/oauth_app" do
+    test "errors", %{conn: conn} do
+      response = conn |> post("/api/pleroma/admin/oauth_app", %{}) |> json_response(200)
+
+      assert response == %{"name" => "can't be blank", "redirect_uris" => "can't be blank"}
+    end
+
+    test "success", %{conn: conn} do
+      base_url = Pleroma.Web.base_url()
+      app_name = "Trusted app"
+
+      response =
+        conn
+        |> post("/api/pleroma/admin/oauth_app", %{
+          name: app_name,
+          redirect_uris: base_url
+        })
+        |> json_response(200)
+
+      assert %{
+               "client_id" => _,
+               "client_secret" => _,
+               "name" => ^app_name,
+               "redirect_uri" => ^base_url,
+               "trusted" => false
+             } = response
+    end
+
+    test "with trusted", %{conn: conn} do
+      base_url = Pleroma.Web.base_url()
+      app_name = "Trusted app"
+
+      response =
+        conn
+        |> post("/api/pleroma/admin/oauth_app", %{
+          name: app_name,
+          redirect_uris: base_url,
+          trusted: true
+        })
+        |> json_response(200)
+
+      assert %{
+               "client_id" => _,
+               "client_secret" => _,
+               "name" => ^app_name,
+               "redirect_uri" => ^base_url,
+               "trusted" => true
+             } = response
+    end
+  end
+
+  describe "GET /api/pleroma/admin/oauth_app" do
+    setup do
+      app = insert(:oauth_app)
+      {:ok, app: app}
+    end
+
+    test "list", %{conn: conn} do
+      response =
+        conn
+        |> get("/api/pleroma/admin/oauth_app")
+        |> json_response(200)
+
+      assert %{"apps" => apps, "count" => count, "page_size" => _} = response
+
+      assert length(apps) == count
+    end
+
+    test "with page size", %{conn: conn} do
+      insert(:oauth_app)
+      page_size = 1
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/oauth_app", %{page_size: to_string(page_size)})
+        |> json_response(200)
+
+      assert %{"apps" => apps, "count" => _, "page_size" => ^page_size} = response
+
+      assert length(apps) == page_size
+    end
+
+    test "search by client name", %{conn: conn, app: app} do
+      response =
+        conn
+        |> get("/api/pleroma/admin/oauth_app", %{name: app.client_name})
+        |> json_response(200)
+
+      assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
+
+      assert returned["client_id"] == app.client_id
+      assert returned["name"] == app.client_name
+    end
+
+    test "search by client id", %{conn: conn, app: app} do
+      response =
+        conn
+        |> get("/api/pleroma/admin/oauth_app", %{client_id: app.client_id})
+        |> json_response(200)
+
+      assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
+
+      assert returned["client_id"] == app.client_id
+      assert returned["name"] == app.client_name
+    end
+
+    test "only trusted", %{conn: conn} do
+      app = insert(:oauth_app, trusted: true)
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/oauth_app", %{trusted: true})
+        |> json_response(200)
+
+      assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
+
+      assert returned["client_id"] == app.client_id
+      assert returned["name"] == app.client_name
+    end
+  end
+
+  describe "DELETE /api/pleroma/admin/oauth_app/:id" do
+    test "with id", %{conn: conn} do
+      app = insert(:oauth_app)
+
+      response =
+        conn
+        |> delete("/api/pleroma/admin/oauth_app/" <> to_string(app.id))
+        |> json_response(:no_content)
+
+      assert response == ""
+    end
+
+    test "with non existance id", %{conn: conn} do
+      response =
+        conn
+        |> delete("/api/pleroma/admin/oauth_app/0")
+        |> json_response(:bad_request)
+
+      assert response == ""
+    end
+  end
+
+  describe "PATCH /api/pleroma/admin/oauth_app/:id" do
+    test "with id", %{conn: conn} do
+      app = insert(:oauth_app)
+
+      name = "another name"
+      url = "https://example.com"
+      scopes = ["admin"]
+      id = app.id
+      website = "http://website.com"
+
+      response =
+        conn
+        |> patch("/api/pleroma/admin/oauth_app/" <> to_string(app.id), %{
+          name: name,
+          trusted: true,
+          redirect_uris: url,
+          scopes: scopes,
+          website: website
+        })
+        |> json_response(200)
+
+      assert %{
+               "client_id" => _,
+               "client_secret" => _,
+               "id" => ^id,
+               "name" => ^name,
+               "redirect_uri" => ^url,
+               "trusted" => true,
+               "website" => ^website
+             } = response
+    end
+
+    test "without id", %{conn: conn} do
+      response =
+        conn
+        |> patch("/api/pleroma/admin/oauth_app/0")
+        |> json_response(:bad_request)
+
+      assert response == ""
+    end
+  end
 end
 
 # Needed for testing
index a9fa0ce48c40f1f6c127aa928676e35f0b862a3b..f770232df20203d8988753d261890e746ce05aa1 100644 (file)
@@ -942,6 +942,73 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       res = post(conn, "/api/v1/accounts", valid_params)
       assert json_response(res, 403) == %{"error" => "Invalid credentials"}
     end
+
+    test "registration from trusted app" do
+      clear_config([Pleroma.Captcha, :enabled], true)
+      app = insert(:oauth_app, trusted: true, scopes: ["read", "write", "follow", "push"])
+
+      conn =
+        build_conn()
+        |> post("/oauth/token", %{
+          "grant_type" => "client_credentials",
+          "client_id" => app.client_id,
+          "client_secret" => app.client_secret
+        })
+
+      assert %{"access_token" => token, "token_type" => "Bearer"} = json_response(conn, 200)
+
+      response =
+        build_conn()
+        |> Plug.Conn.put_req_header("authorization", "Bearer " <> token)
+        |> post("/api/v1/accounts", %{
+          nickname: "nickanme",
+          agreement: true,
+          email: "email@example.com",
+          fullname: "Lain",
+          username: "Lain",
+          password: "some_password",
+          confirm: "some_password"
+        })
+        |> json_response(200)
+
+      assert %{
+               "access_token" => access_token,
+               "created_at" => _,
+               "scope" => ["read", "write", "follow", "push"],
+               "token_type" => "Bearer"
+             } = response
+
+      response =
+        build_conn()
+        |> Plug.Conn.put_req_header("authorization", "Bearer " <> access_token)
+        |> get("/api/v1/accounts/verify_credentials")
+        |> json_response(200)
+
+      assert %{
+               "acct" => "Lain",
+               "bot" => false,
+               "display_name" => "Lain",
+               "follow_requests_count" => 0,
+               "followers_count" => 0,
+               "following_count" => 0,
+               "locked" => false,
+               "note" => "",
+               "source" => %{
+                 "fields" => [],
+                 "note" => "",
+                 "pleroma" => %{
+                   "actor_type" => "Person",
+                   "discoverable" => false,
+                   "no_rich_text" => false,
+                   "show_role" => true
+                 },
+                 "privacy" => "public",
+                 "sensitive" => false
+               },
+               "statuses_count" => 0,
+               "username" => "Lain"
+             } = response
+    end
   end
 
   describe "create account by app / rate limit" do