diff options
Diffstat (limited to 'lib/pleroma/web/mastodon_api')
5 files changed, 94 insertions, 95 deletions
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 12e3ba15e..25e499a77 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -24,6 +24,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.ScheduledActivityView + plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(:skip_plug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show]) @unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []} @@ -97,12 +98,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.StatusOperation + @doc """ GET `/api/v1/statuses?ids[]=1&ids[]=2` `ids` query param is required """ - def index(%{assigns: %{user: user}} = conn, %{"ids" => ids} = params) do + def index(%{assigns: %{user: user}} = conn, %{ids: ids} = params) do limit = 100 activities = @@ -125,21 +128,29 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do Creates a scheduled status when `scheduled_at` param is present and it's far enough """ def create( - %{assigns: %{user: user}} = conn, - %{"status" => _, "scheduled_at" => scheduled_at} = params + %{ + assigns: %{user: user}, + body_params: %{status: _, scheduled_at: scheduled_at} = params + } = conn, + _ ) when not is_nil(scheduled_at) do - params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"]) + params = Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id]) + + attrs = %{ + params: Map.new(params, fn {key, value} -> {to_string(key), value} end), + scheduled_at: scheduled_at + } with {:far_enough, true} <- {:far_enough, ScheduledActivity.far_enough?(scheduled_at)}, - attrs <- %{"params" => params, "scheduled_at" => scheduled_at}, {:ok, scheduled_activity} <- ScheduledActivity.create(user, attrs) do conn |> put_view(ScheduledActivityView) |> render("show.json", scheduled_activity: scheduled_activity) else {:far_enough, _} -> - create(conn, Map.drop(params, ["scheduled_at"])) + params = Map.drop(params, [:scheduled_at]) + create(%Plug.Conn{conn | body_params: params}, %{}) error -> error @@ -151,8 +162,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do Creates a regular status """ - def create(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do - params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"]) + def create(%{assigns: %{user: user}, body_params: %{status: _} = params} = conn, _) do + params = Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id]) with {:ok, activity} <- CommonAPI.post(user, params) do try_render(conn, "show.json", @@ -169,12 +180,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end end - def create(%{assigns: %{user: _user}} = conn, %{"media_ids" => _} = params) do - create(conn, Map.put(params, "status", "")) + def create(%{assigns: %{user: _user}, body_params: %{media_ids: _} = params} = conn, _) do + params = Map.put(params, :status, "") + create(%Plug.Conn{conn | body_params: params}, %{}) end @doc "GET /api/v1/statuses/:id" - def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do + def show(%{assigns: %{user: user}} = conn, %{id: id}) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), true <- Visibility.visible_for_user?(activity, user) do try_render(conn, "show.json", @@ -188,7 +200,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "DELETE /api/v1/statuses/:id" - def delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do + def delete(%{assigns: %{user: user}} = conn, %{id: id}) do with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do json(conn, %{}) else @@ -198,7 +210,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/reblog" - def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id} = params) do + def reblog(%{assigns: %{user: user}, body_params: params} = conn, %{id: ap_id_or_id}) do with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user, params), %Activity{} = announce <- Activity.normalize(announce.data) do try_render(conn, "show.json", %{activity: announce, for: user, as: :activity}) @@ -206,7 +218,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/unreblog" - def unreblog(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do + def unreblog(%{assigns: %{user: user}} = conn, %{id: activity_id}) do with {:ok, _unannounce} <- CommonAPI.unrepeat(activity_id, user), %Activity{} = activity <- Activity.get_by_id(activity_id) do try_render(conn, "show.json", %{activity: activity, for: user, as: :activity}) @@ -214,7 +226,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/favourite" - def favourite(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do + def favourite(%{assigns: %{user: user}} = conn, %{id: activity_id}) do with {:ok, _fav} <- CommonAPI.favorite(user, activity_id), %Activity{} = activity <- Activity.get_by_id(activity_id) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) @@ -222,7 +234,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/unfavourite" - def unfavourite(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do + def unfavourite(%{assigns: %{user: user}} = conn, %{id: activity_id}) do with {:ok, _unfav} <- CommonAPI.unfavorite(activity_id, user), %Activity{} = activity <- Activity.get_by_id(activity_id) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) @@ -230,21 +242,21 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/pin" - def pin(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do + def pin(%{assigns: %{user: user}} = conn, %{id: ap_id_or_id}) do with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) end end @doc "POST /api/v1/statuses/:id/unpin" - def unpin(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do + def unpin(%{assigns: %{user: user}} = conn, %{id: ap_id_or_id}) do with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) end end @doc "POST /api/v1/statuses/:id/bookmark" - def bookmark(%{assigns: %{user: user}} = conn, %{"id" => id}) do + def bookmark(%{assigns: %{user: user}} = conn, %{id: id}) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), %User{} = user <- User.get_cached_by_nickname(user.nickname), true <- Visibility.visible_for_user?(activity, user), @@ -254,7 +266,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/unbookmark" - def unbookmark(%{assigns: %{user: user}} = conn, %{"id" => id}) do + def unbookmark(%{assigns: %{user: user}} = conn, %{id: id}) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), %User{} = user <- User.get_cached_by_nickname(user.nickname), true <- Visibility.visible_for_user?(activity, user), @@ -264,7 +276,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/mute" - def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do + def mute_conversation(%{assigns: %{user: user}} = conn, %{id: id}) do with %Activity{} = activity <- Activity.get_by_id(id), {:ok, activity} <- CommonAPI.add_mute(user, activity) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) @@ -272,7 +284,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/unmute" - def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do + def unmute_conversation(%{assigns: %{user: user}} = conn, %{id: id}) do with %Activity{} = activity <- Activity.get_by_id(id), {:ok, activity} <- CommonAPI.remove_mute(user, activity) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) @@ -281,7 +293,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do @doc "GET /api/v1/statuses/:id/card" @deprecated "https://github.com/tootsuite/mastodon/pull/11213" - def card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do + def card(%{assigns: %{user: user}} = conn, %{id: status_id}) do with %Activity{} = activity <- Activity.get_by_id(status_id), true <- Visibility.visible_for_user?(activity, user) do data = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) @@ -292,7 +304,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "GET /api/v1/statuses/:id/favourited_by" - def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do + def favourited_by(%{assigns: %{user: user}} = conn, %{id: id}) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, %Object{data: %{"likes" => likes}} <- Object.normalize(activity) do @@ -312,7 +324,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "GET /api/v1/statuses/:id/reblogged_by" - def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do + def reblogged_by(%{assigns: %{user: user}} = conn, %{id: id}) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, %Object{data: %{"announcements" => announces, "id" => ap_id}} <- @@ -344,7 +356,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "GET /api/v1/statuses/:id/context" - def context(%{assigns: %{user: user}} = conn, %{"id" => id}) do + def context(%{assigns: %{user: user}} = conn, %{id: id}) do with %Activity{} = activity <- Activity.get_by_id(id) do activities = ActivityPub.fetch_activities_for_context(activity.data["context"], %{ @@ -359,11 +371,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do @doc "GET /api/v1/favourites" def favourites(%{assigns: %{user: %User{} = user}} = conn, params) do - activities = - ActivityPub.fetch_favourites( - user, - Map.take(params, Pleroma.Pagination.page_keys()) - ) + params = + params + |> Map.new(fn {key, value} -> {to_string(key), value} end) + |> Map.take(Pleroma.Pagination.page_keys()) + + activities = ActivityPub.fetch_favourites(user, params) conn |> add_link_headers(activities) diff --git a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex index c93a43969..f91df9ab7 100644 --- a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex @@ -5,11 +5,26 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionController do use Pleroma.Web, :controller - alias Pleroma.Plugs.OAuthScopesPlug - require Logger - plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :index) + plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["read"]} when action == :index) + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %OpenApiSpex.Operation{ + tags: ["Suggestions"], + summary: "Follow suggestions (Not implemented)", + operationId: "SuggestionController.index", + responses: %{ + 200 => Pleroma.Web.ApiSpec.Helpers.empty_array_response() + } + } + end @doc "GET /api/v1/suggestions" def index(conn, params), diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 2d67e19da..bbd576ffd 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do use Pleroma.Web, :controller import Pleroma.Web.ControllerHelper, - only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1, skip_relationships?: 1] + only: [add_link_headers: 2, add_link_headers: 3, skip_relationships?: 1] alias Pleroma.Pagination alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug @@ -15,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub + plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:public, :hashtag]) # TODO: Replace with a macro when there is a Phoenix release with the following commit in it: @@ -37,10 +38,13 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do plug(:put_view, Pleroma.Web.MastodonAPI.StatusView) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TimelineOperation + # GET /api/v1/timelines/home def home(%{assigns: %{user: user}} = conn, params) do params = params + |> Map.new(fn {key, value} -> {to_string(key), value} end) |> Map.put("type", ["Create", "Announce"]) |> Map.put("blocking_user", user) |> Map.put("muting_user", user) @@ -68,6 +72,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do def direct(%{assigns: %{user: user}} = conn, params) do params = params + |> Map.new(fn {key, value} -> {to_string(key), value} end) |> Map.put("type", "Create") |> Map.put("blocking_user", user) |> Map.put("user", user) @@ -90,7 +95,9 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do # GET /api/v1/timelines/public def public(%{assigns: %{user: user}} = conn, params) do - local_only = truthy_param?(params["local"]) + params = Map.new(params, fn {key, value} -> {to_string(key), value} end) + + local_only = params["local"] cfg_key = if local_only do @@ -157,8 +164,8 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do # GET /api/v1/timelines/tag/:tag def hashtag(%{assigns: %{user: user}} = conn, params) do - local_only = truthy_param?(params["local"]) - + params = Map.new(params, fn {key, value} -> {to_string(key), value} end) + local_only = params["local"] activities = hashtag_fetching(params, user, local_only) conn @@ -172,10 +179,11 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do end # GET /api/v1/timelines/list/:list_id - def list(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do + def list(%{assigns: %{user: user}} = conn, %{list_id: id} = params) do with %Pleroma.List{title: _title, following: following} <- Pleroma.List.get(id, user) do params = params + |> Map.new(fn {key, value} -> {to_string(key), value} end) |> Map.put("type", "Create") |> Map.put("blocking_user", user) |> Map.put("user", user) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 2feda6807..19fbf0902 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -338,7 +338,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do defp maybe_put_role(data, _, _), do: data defp maybe_put_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do - Kernel.put_in(data, [:pleroma, :notification_settings], user.notification_settings) + Kernel.put_in( + data, + [:pleroma, :notification_settings], + Map.from_struct(user.notification_settings) + ) end defp maybe_put_notification_settings(data, _, _), do: data diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 393d093e5..94e4595d8 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -19,26 +19,12 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do # Hibernate every X messages @hibernate_every 100 - @streams [ - "public", - "public:local", - "public:media", - "public:local:media", - "user", - "user:notification", - "direct", - "list", - "hashtag" - ] - @anonymous_streams ["public", "public:local", "hashtag"] - def init(%{qs: qs} = req, state) do - with params <- :cow_qs.parse_qs(qs), + with params <- Enum.into(:cow_qs.parse_qs(qs), %{}), sec_websocket <- :cowboy_req.header("sec-websocket-protocol", req, nil), - access_token <- List.keyfind(params, "access_token", 0), - {_, stream} <- List.keyfind(params, "stream", 0), - {:ok, user} <- allow_request(stream, [access_token, sec_websocket]), - topic when is_binary(topic) <- expand_topic(stream, params) do + access_token <- Map.get(params, "access_token"), + {:ok, user} <- authenticate_request(access_token, sec_websocket), + {:ok, topic} <- Streamer.get_topic(Map.get(params, "stream"), user, params) do req = if sec_websocket do :cowboy_req.set_resp_header("sec-websocket-protocol", sec_websocket, req) @@ -49,14 +35,14 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do {:cowboy_websocket, req, %{user: user, topic: topic, count: 0, timer: nil}, %{idle_timeout: @timeout}} else - {:error, code} -> - Logger.debug("#{__MODULE__} denied connection: #{inspect(code)} - #{inspect(req)}") - {:ok, req} = :cowboy_req.reply(code, req) + {:error, :bad_topic} -> + Logger.debug("#{__MODULE__} bad topic #{inspect(req)}") + {:ok, req} = :cowboy_req.reply(404, req) {:ok, req, state} - error -> - Logger.debug("#{__MODULE__} denied connection: #{inspect(error)} - #{inspect(req)}") - {:ok, req} = :cowboy_req.reply(400, req) + {:error, :unauthorized} -> + Logger.debug("#{__MODULE__} authentication error: #{inspect(req)}") + {:ok, req} = :cowboy_req.reply(401, req) {:ok, req, state} end end @@ -124,50 +110,23 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do end # Public streams without authentication. - defp allow_request(stream, [nil, nil]) when stream in @anonymous_streams do + defp authenticate_request(nil, nil) do {:ok, nil} end # Authenticated streams. - defp allow_request(stream, [access_token, sec_websocket]) when stream in @streams do - token = - with {"access_token", token} <- access_token do - token - else - _ -> sec_websocket - end + defp authenticate_request(access_token, sec_websocket) do + token = access_token || sec_websocket with true <- is_bitstring(token), %Token{user_id: user_id} <- Repo.get_by(Token, token: token), user = %User{} <- User.get_cached_by_id(user_id) do {:ok, user} else - _ -> {:error, 403} - end - end - - # Not authenticated. - defp allow_request(stream, _) when stream in @streams, do: {:error, 403} - - # No matching stream. - defp allow_request(_, _), do: {:error, 404} - - defp expand_topic("hashtag", params) do - case List.keyfind(params, "tag", 0) do - {_, tag} -> "hashtag:#{tag}" - _ -> nil + _ -> {:error, :unauthorized} end end - defp expand_topic("list", params) do - case List.keyfind(params, "list", 0) do - {_, list} -> "list:#{list}" - _ -> nil - end - end - - defp expand_topic(topic, _), do: topic - defp timer do Process.send_after(self(), :tick, @tick) end |