aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/pleroma/web/api_spec/operations/o_auth_operation.ex250
-rw-r--r--lib/pleroma/web/auth/authenticator.ex2
-rw-r--r--lib/pleroma/web/o_auth/o_auth_controller.ex37
-rw-r--r--lib/pleroma/web/o_auth/token/utils.ex2
-rw-r--r--lib/pleroma/web/router.ex8
5 files changed, 281 insertions, 18 deletions
diff --git a/lib/pleroma/web/api_spec/operations/o_auth_operation.ex b/lib/pleroma/web/api_spec/operations/o_auth_operation.ex
new file mode 100644
index 000000000..d507fddd5
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/o_auth_operation.ex
@@ -0,0 +1,250 @@
+# 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.OAuthOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ defp client_id_parameter(opts) do
+ Operation.parameter(
+ :client_id,
+ :query,
+ :string,
+ "Client ID, obtained during app registration",
+ opts
+ )
+ end
+
+ defp client_secret_parameter(opts) do
+ Operation.parameter(
+ :client_secret,
+ :query,
+ :string,
+ "Client secret, obtained during app registration",
+ opts
+ )
+ end
+
+ defp redirect_uri_parameter(opts) do
+ Operation.parameter(
+ :redirect_uri,
+ :query,
+ :string,
+ "Set a URI to redirect the user to. If this parameter is set to `urn:ietf:wg:oauth:2.0:oob` then the token will be shown instead. Must match one of the redirect URIs declared during app registration.",
+ opts
+ )
+ end
+
+ defp scope_parameter(opts) do
+ Operation.parameter(
+ :scope,
+ :query,
+ :string,
+ "List of requested OAuth scopes, separated by spaces. Must be a subset of scopes declared during app registration. If not provided, defaults to `read`.",
+ opts
+ )
+ end
+
+ def token_exchange_operation do
+ %Operation{
+ tags: ["OAuth"],
+ summary: "Access Token Request",
+ operationId: "OAuthController.token_exchange",
+ parameters: [
+ # code is required when grant_type == "authorization_code"
+ # Mastodon requires `redirect_uri`, we don't
+ client_id_parameter(required: true),
+ client_secret_parameter(required: true),
+ redirect_uri_parameter([]),
+ scope_parameter([]),
+ Operation.parameter(
+ :code,
+ :query,
+ :string,
+ "A user authorization code, obtained via /oauth/authorize"
+ ),
+ Operation.parameter(
+ :grant_type,
+ :query,
+ :string,
+ "Set equal to `authorization_code` if `code` is provided in order to gain user-level access. Set equal to `password` if `username` and `password` are provided. Otherwise, set equal to `client_credentials` to obtain app-level access only.",
+ required: true
+ ),
+ Operation.parameter(:username, :query, :string, "User's username, used with `grant_type=password`"),
+ Operation.parameter(:password, :query, :string, "User's password, used with `grant_type=password`")
+ ],
+ responses: %{
+ 200 =>
+ Operation.response("Success", "application/json", %Schema{
+ type: :object,
+ properties: %{status: %Schema{type: :string, example: "success"}}
+ }),
+ 400 => Operation.response("Error", "application/json", ApiError),
+ 403 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ def token_revoke_operation do
+ %Operation{
+ tags: ["OAuth"],
+ summary: "Revokes token",
+ operationId: "OAuthController.token_revoke",
+ parameters: [],
+ responses: %{
+ 200 =>
+ Operation.response("Success", "application/json", %Schema{
+ type: :object,
+ properties: %{status: %Schema{type: :string, example: "success"}}
+ }),
+ 400 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ def registration_details_operation do
+ %Operation{
+ tags: ["OAuth"],
+ summary: "Register",
+ operationId: "OAuthController.registration_details",
+ parameters: [],
+ responses: %{
+ 200 =>
+ Operation.response("Success", "application/json", %Schema{
+ type: :object,
+ properties: %{status: %Schema{type: :string, example: "success"}}
+ }),
+ 400 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ def authorize_operation do
+ %Operation{
+ tags: ["OAuth"],
+ summary: "OAuth callback",
+ operationId: "OAuthController.authorize",
+ parameters: [
+ client_id_parameter(required: true),
+ client_secret_parameter([]),
+ Operation.parameter(
+ :response_type,
+ :query,
+ :string,
+ "Note: `code` is the only value supported (MastodonAPI and OAuth 2.1)",
+ required: true
+ ),
+ redirect_uri_parameter([]),
+ scope_parameter([]),
+ Operation.parameter(
+ :state,
+ :query,
+ :string,
+ "An opaque value used by the client to maintain state between the request and callback. The authorization server includes this value when redirecting the user-agent back to the client."
+ )
+ ],
+ responses: %{
+ 200 =>
+ Operation.response("Success", "application/json", %Schema{
+ type: :object,
+ properties: %{status: %Schema{type: :string, example: "success"}}
+ }),
+ 400 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ def create_authorization_operation do
+ %Operation{
+ tags: ["OAuth"],
+ summary: "Create Authorization",
+ operationId: "OAuthController.create_authorization",
+ parameters: [],
+ responses: %{
+ 200 =>
+ Operation.response("Success", "application/json", %Schema{
+ type: :object,
+ properties: %{status: %Schema{type: :string, example: "success"}}
+ }),
+ 400 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ def prepare_request_operation do
+ %Operation{
+ tags: ["OAuth"],
+ summary: "Prepare OAuth request for third-party auth providers",
+ operationId: "OAuthController.prepare_request",
+ parameters: [],
+ responses: %{
+ 302 =>
+ Operation.response("Success", "text/html", %Schema{
+ type: :object,
+ properties: %{status: %Schema{type: :string, example: "success"}}
+ }),
+ 400 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ # The following operations should be moved to another controller, they aren't meant to be into OpenAPI
+
+ def request_operation do
+ %Operation{
+ tags: ["OAuth"],
+ summary: "",
+ operationId: "OAuthController.request",
+ parameters: [],
+ responses: %{
+ 200 =>
+ Operation.response("Success", "application/json", %Schema{
+ type: :object,
+ properties: %{status: %Schema{type: :string, example: "success"}}
+ }),
+ 400 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ def register_operation do
+ %Operation{
+ tags: ["OAuth"],
+ summary: "",
+ operationId: "OAuthController.register",
+ parameters: [],
+ responses: %{
+ 200 =>
+ Operation.response("Success", "application/json", %Schema{
+ type: :object,
+ properties: %{status: %Schema{type: :string, example: "success"}}
+ }),
+ 400 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ def callback_operation do
+ %Operation{
+ tags: ["OAuth"],
+ summary: "",
+ operationId: "OAuthController.callback",
+ parameters: [],
+ responses: %{
+ 200 =>
+ Operation.response("Success", "application/json", %Schema{
+ type: :object,
+ properties: %{status: %Schema{type: :string, example: "success"}}
+ }),
+ 400 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+end
diff --git a/lib/pleroma/web/auth/authenticator.ex b/lib/pleroma/web/auth/authenticator.ex
index 84741ee11..496c3973c 100644
--- a/lib/pleroma/web/auth/authenticator.ex
+++ b/lib/pleroma/web/auth/authenticator.ex
@@ -60,7 +60,7 @@ defmodule Pleroma.Web.Auth.Authenticator do
%{"authorization" => %{"name" => name, "password" => password}} ->
{:ok, {name, password}}
- %{"grant_type" => "password", "username" => name, "password" => password} ->
+ %{grant_type: "password", username: name, password: password} ->
{:ok, {name, password}}
_ ->
diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex
index 215d97b3a..741f57195 100644
--- a/lib/pleroma/web/o_auth/o_auth_controller.ex
+++ b/lib/pleroma/web/o_auth/o_auth_controller.ex
@@ -29,6 +29,11 @@ defmodule Pleroma.Web.OAuth.OAuthController do
if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
+ plug(
+ Pleroma.Web.ApiSpec.CastAndValidate
+ when action not in [:prepare_request, :callback, :request, :register]
+ )
+
plug(:fetch_session)
plug(:fetch_flash)
@@ -43,14 +48,16 @@ defmodule Pleroma.Web.OAuth.OAuthController do
@oob_token_redirect_uri "urn:ietf:wg:oauth:2.0:oob"
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.OAuthOperation
+
# Note: this definition is only called from error-handling methods with `conn.params` as 2nd arg
- def authorize(%Plug.Conn{} = conn, %{"authorization" => _} = params) do
- {auth_attrs, params} = Map.pop(params, "authorization")
+ def authorize(%Plug.Conn{} = conn, %{authorization: _} = params) do
+ {auth_attrs, params} = Map.pop(params, :authorization)
authorize(conn, Map.merge(params, auth_attrs))
end
- def authorize(%Plug.Conn{assigns: %{token: %Token{}}} = conn, %{"force_login" => _} = params) do
- if ControllerHelper.truthy_param?(params["force_login"]) do
+ def authorize(%Plug.Conn{assigns: %{token: %Token{}}} = conn, %{force_login: _} = params) do
+ if ControllerHelper.truthy_param?(params[:force_login]) do
do_authorize(conn, params)
else
handle_existing_authorization(conn, params)
@@ -63,7 +70,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
# So we have to check client and token.
def authorize(
%Plug.Conn{assigns: %{token: %Token{} = token}} = conn,
- %{"client_id" => client_id} = params
+ %{client_id: client_id} = params
) do
with %Token{} = t <- Repo.get_by(Token, token: token.token) |> Repo.preload(:app),
^client_id <- t.app.client_id do
@@ -147,7 +154,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
create_authorization(conn, params, user: user)
end
- def create_authorization(%Plug.Conn{} = conn, %{"authorization" => _} = params, opts) do
+ def create_authorization(%Plug.Conn{} = conn, %{authorization: _} = params, opts) do
with {:ok, auth, user} <- do_create_authorization(conn, params, opts[:user]),
{:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)} do
after_create_authorization(conn, auth, params)
@@ -255,7 +262,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
@doc "Renew access_token with refresh_token"
def token_exchange(
%Plug.Conn{} = conn,
- %{"grant_type" => "refresh_token", "refresh_token" => token} = _params
+ %{grant_type: "refresh_token", refresh_token: token} = _params
) do
with {:ok, app} <- Token.Utils.fetch_app(conn),
{:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
@@ -266,9 +273,9 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
end
- def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "authorization_code"} = params) do
+ def token_exchange(%Plug.Conn{} = conn, %{grant_type: "authorization_code"} = params) do
with {:ok, app} <- Token.Utils.fetch_app(conn),
- fixed_token = Token.Utils.fix_padding(params["code"]),
+ fixed_token = Token.Utils.fix_padding(params[:code]),
{:ok, auth} <- Authorization.get_by_token(app, fixed_token),
%User{} = user <- User.get_cached_by_id(auth.user_id),
{:ok, token} <- Token.exchange_token(app, auth) do
@@ -281,7 +288,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
def token_exchange(
%Plug.Conn{} = conn,
- %{"grant_type" => "password"} = params
+ %{grant_type: "password"} = params
) do
with {:ok, %User{} = user} <- Authenticator.get_user(conn),
{:ok, app} <- Token.Utils.fetch_app(conn),
@@ -296,7 +303,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
def token_exchange(
%Plug.Conn{} = conn,
- %{"grant_type" => "password", "name" => name, "password" => _password} = params
+ %{grant_type: "password", name: name, password: _password} = params
) do
params =
params
@@ -306,7 +313,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
token_exchange(conn, params)
end
- def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "client_credentials"} = _params) do
+ def token_exchange(%Plug.Conn{} = conn, %{grant_type: "client_credentials"} = _params) do
with {:ok, app} <- Token.Utils.fetch_app(conn),
{:ok, auth} <- Authorization.create_authorization(app, %User{}),
{:ok, token} <- Token.exchange_token(app, auth) do
@@ -379,7 +386,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
render_invalid_credentials_error(conn)
end
- def token_revoke(%Plug.Conn{} = conn, %{"token" => token}) do
+ def token_revoke(%Plug.Conn{} = conn, %{token: token}) do
with {:ok, %Token{} = oauth_token} <- Token.get_by_token(token),
{:ok, oauth_token} <- RevokeToken.revoke(oauth_token) do
conn =
@@ -477,7 +484,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
conn
|> put_session_registration_id(registration.id)
- |> registration_details(%{"authorization" => registration_params})
+ |> registration_details(%{authorization: registration_params})
end
else
error ->
@@ -493,7 +500,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
Map.merge(params, Jason.decode!(state))
end
- def registration_details(%Plug.Conn{} = conn, %{"authorization" => auth_attrs}) do
+ def registration_details(%Plug.Conn{} = conn, %{authorization: auth_attrs}) do
render(conn, "register.html", %{
client_id: auth_attrs["client_id"],
redirect_uri: auth_attrs["redirect_uri"],
diff --git a/lib/pleroma/web/o_auth/token/utils.ex b/lib/pleroma/web/o_auth/token/utils.ex
index b572dc9cf..11ee34c72 100644
--- a/lib/pleroma/web/o_auth/token/utils.ex
+++ b/lib/pleroma/web/o_auth/token/utils.ex
@@ -41,7 +41,7 @@ defmodule Pleroma.Web.OAuth.Token.Utils do
) do
{id, secret}
else
- _ -> {conn.params["client_id"], conn.params["client_secret"]}
+ _ -> {conn.params[:client_id], conn.params[:client_secret]}
end
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 8ba0fc702..34df3f365 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -30,11 +30,17 @@ defmodule Pleroma.Web.Router do
plug(:fetch_session)
end
+ pipeline :fetch_session_api do
+ plug(:fetch_session)
+ plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
+ end
+
pipeline :oauth do
plug(:fetch_session)
plug(Pleroma.Web.Plugs.OAuthPlug)
plug(Pleroma.Web.Plugs.UserEnabledPlug)
plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug)
+ plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
end
# Note: expects _user_ authentication (user-unbound app-bound tokens don't qualify)
@@ -344,7 +350,7 @@ defmodule Pleroma.Web.Router do
end
scope [] do
- pipe_through(:fetch_session)
+ pipe_through(:fetch_session_api)
post("/token", OAuthController, :token_exchange)
post("/revoke", OAuthController, :token_revoke)