[#2510] Improved support for app-bound OAuth tokens. Auth-related refactoring.

This commit is contained in:
Ivan Tashkinov 2021-02-11 15:02:50 +03:00
parent 2cf753c502
commit df89b5019b
7 changed files with 70 additions and 34 deletions

View file

@ -3,6 +3,11 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.AppController do defmodule Pleroma.Web.MastodonAPI.AppController do
@moduledoc """
Controller for supporting app-related actions.
If authentication is an option, app tokens (user-unbound) must be supported.
"""
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Repo alias Pleroma.Repo
@ -17,11 +22,9 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
plug( plug(
:skip_plug, :skip_plug,
[OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug]
when action == :create when action in [:create, :verify_credentials]
) )
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials)
plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(Pleroma.Web.ApiSpec.CastAndValidate)
@local_mastodon_name "Mastodon-Local" @local_mastodon_name "Mastodon-Local"
@ -44,10 +47,13 @@ def create(%{body_params: params} = conn, _params) do
end end
end end
@doc "GET /api/v1/apps/verify_credentials" @doc """
def verify_credentials(%{assigns: %{user: _user, token: token}} = conn, _) do GET /api/v1/apps/verify_credentials
with %Token{app: %App{} = app} <- Repo.preload(token, :app) do Gets compact non-secret representation of the app. Supports app tokens and user tokens.
render(conn, "short.json", app: app) """
def verify_credentials(%{assigns: %{token: %Token{} = token}} = conn, _) do
with %{app: %App{} = app} <- Repo.preload(token, :app) do
render(conn, "compact_non_secret.json", app: app)
end end
end end
end end

View file

@ -34,10 +34,10 @@ def render("show.json", %{app: %App{} = app}) do
|> with_vapid_key() |> with_vapid_key()
end end
def render("short.json", %{app: %App{website: webiste, client_name: name}}) do def render("compact_non_secret.json", %{app: %App{website: website, client_name: name}}) do
%{ %{
name: name, name: name,
website: webiste website: website
} }
|> with_vapid_key() |> with_vapid_key()
end end

View file

@ -3,6 +3,10 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.EnsureAuthenticatedPlug do defmodule Pleroma.Web.Plugs.EnsureAuthenticatedPlug do
@moduledoc """
Ensures _user_ authentication (app-bound user-unbound tokens are not accepted).
"""
import Plug.Conn import Plug.Conn
import Pleroma.Web.TranslationHelpers import Pleroma.Web.TranslationHelpers

View file

@ -3,6 +3,11 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug do defmodule Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug do
@moduledoc """
Ensures instance publicity or _user_ authentication
(app-bound user-unbound tokens are accepted only if the instance is public).
"""
import Pleroma.Web.TranslationHelpers import Pleroma.Web.TranslationHelpers
import Plug.Conn import Plug.Conn

View file

@ -28,6 +28,11 @@ def call(%{assigns: %{user: %User{id: user_id}} = assigns} = conn, _) do
end end
end end
# App-bound token case (obtained with client_id and client_secret)
def call(%{assigns: %{token: %Token{user_id: nil}}} = conn, _) do
assign(conn, :user, nil)
end
def call(conn, _) do def call(conn, _) do
conn conn
|> assign(:user, nil) |> assign(:user, nil)

View file

@ -37,11 +37,13 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug) plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug)
end end
pipeline :expect_authentication do # Note: expects _user_ authentication (user-unbound app-bound tokens don't qualify)
pipeline :expect_user_authentication do
plug(Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug) plug(Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug)
end end
pipeline :expect_public_instance_or_authentication do # Note: expects public instance or _user_ authentication (user-unbound tokens don't qualify)
pipeline :expect_public_instance_or_user_authentication do
plug(Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug) plug(Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug)
end end
@ -66,23 +68,30 @@ defmodule Pleroma.Web.Router do
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec) plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
end end
pipeline :api do pipeline :no_auth_or_privacy_expectations_api do
plug(:expect_public_instance_or_authentication)
plug(:base_api) plug(:base_api)
plug(:after_auth) plug(:after_auth)
plug(Pleroma.Web.Plugs.IdempotencyPlug) plug(Pleroma.Web.Plugs.IdempotencyPlug)
end end
# Pipeline for app-related endpoints (no user auth checks — app-bound tokens must be supported)
pipeline :app_api do
plug(:no_auth_or_privacy_expectations_api)
end
pipeline :api do
plug(:expect_public_instance_or_user_authentication)
plug(:no_auth_or_privacy_expectations_api)
end
pipeline :authenticated_api do pipeline :authenticated_api do
plug(:expect_authentication) plug(:expect_user_authentication)
plug(:base_api) plug(:no_auth_or_privacy_expectations_api)
plug(:after_auth)
plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug) plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug)
plug(Pleroma.Web.Plugs.IdempotencyPlug)
end end
pipeline :admin_api do pipeline :admin_api do
plug(:expect_authentication) plug(:expect_user_authentication)
plug(:base_api) plug(:base_api)
plug(Pleroma.Web.Plugs.AdminSecretAuthenticationPlug) plug(Pleroma.Web.Plugs.AdminSecretAuthenticationPlug)
plug(:after_auth) plug(:after_auth)
@ -432,8 +441,6 @@ defmodule Pleroma.Web.Router do
post("/accounts/:id/mute", AccountController, :mute) post("/accounts/:id/mute", AccountController, :mute)
post("/accounts/:id/unmute", AccountController, :unmute) post("/accounts/:id/unmute", AccountController, :unmute)
get("/apps/verify_credentials", AppController, :verify_credentials)
get("/conversations", ConversationController, :index) get("/conversations", ConversationController, :index)
post("/conversations/:id/read", ConversationController, :mark_as_read) post("/conversations/:id/read", ConversationController, :mark_as_read)
@ -524,6 +531,13 @@ defmodule Pleroma.Web.Router do
put("/settings", MastoFEController, :put_settings) put("/settings", MastoFEController, :put_settings)
end end
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:app_api)
post("/apps", AppController, :create)
get("/apps/verify_credentials", AppController, :verify_credentials)
end
scope "/api/v1", Pleroma.Web.MastodonAPI do scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:api) pipe_through(:api)
@ -540,8 +554,6 @@ defmodule Pleroma.Web.Router do
get("/instance", InstanceController, :show) get("/instance", InstanceController, :show)
get("/instance/peers", InstanceController, :peers) get("/instance/peers", InstanceController, :peers)
post("/apps", AppController, :create)
get("/statuses", StatusController, :index) get("/statuses", StatusController, :index)
get("/statuses/:id", StatusController, :show) get("/statuses/:id", StatusController, :show)
get("/statuses/:id/context", StatusController, :context) get("/statuses/:id/context", StatusController, :context)

View file

@ -12,22 +12,26 @@ defmodule Pleroma.Web.MastodonAPI.AppControllerTest do
import Pleroma.Factory import Pleroma.Factory
test "apps/verify_credentials", %{conn: conn} do test "apps/verify_credentials", %{conn: conn} do
token = insert(:oauth_token) user_bound_token = insert(:oauth_token)
app_bound_token = insert(:oauth_token, user: nil)
refute app_bound_token.user
conn = for token <- [app_bound_token, user_bound_token] do
conn conn =
|> put_req_header("authorization", "Bearer #{token.token}") conn
|> get("/api/v1/apps/verify_credentials") |> put_req_header("authorization", "Bearer #{token.token}")
|> get("/api/v1/apps/verify_credentials")
app = Repo.preload(token, :app).app app = Repo.preload(token, :app).app
expected = %{ expected = %{
"name" => app.client_name, "name" => app.client_name,
"website" => app.website, "website" => app.website,
"vapid_key" => Push.vapid_config() |> Keyword.get(:public_key) "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
} }
assert expected == json_response_and_validate_schema(conn, 200) assert expected == json_response_and_validate_schema(conn, 200)
end
end end
test "creates an oauth app", %{conn: conn} do test "creates an oauth app", %{conn: conn} do