From 88b16fdfb7b40877aecae5d45f6f3a1c54362f13 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 11 Apr 2020 16:01:09 +0300 Subject: [#1364] Disabled notifications on activities from blocked domains. --- lib/pleroma/notification.ex | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 04ee510b9..02363ddb0 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -321,10 +321,11 @@ defmodule Pleroma.Notification do @doc """ Returns a tuple with 2 elements: - {enabled notification receivers, currently disabled receivers (blocking / [thread] muting)} + {notification-enabled receivers, currently disabled receivers (blocking / [thread] muting)} NOTE: might be called for FAKE Activities, see ActivityPub.Utils.get_notified_from_object/1 """ + @spec get_notified_from_activity(Activity.t(), boolean()) :: {list(User.t()), list(User.t())} def get_notified_from_activity(activity, local_only \\ true) def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only) @@ -337,17 +338,22 @@ defmodule Pleroma.Notification do |> Utils.maybe_notify_followers(activity) |> Enum.uniq() - # Since even subscribers and followers can mute / thread-mute, filtering all above AP IDs - notification_enabled_ap_ids = - potential_receiver_ap_ids - |> exclude_relationship_restricted_ap_ids(activity) - |> exclude_thread_muter_ap_ids(activity) - potential_receivers = potential_receiver_ap_ids |> Enum.uniq() |> User.get_users_from_set(local_only) + activity_actor_domain = activity.actor && URI.parse(activity.actor).host + + notification_enabled_ap_ids = + for u <- potential_receivers, activity_actor_domain not in u.domain_blocks, do: u.ap_id + + # Since even subscribers and followers can mute / thread-mute, filtering all above AP IDs + notification_enabled_ap_ids = + notification_enabled_ap_ids + |> exclude_relationship_restricted_ap_ids(activity) + |> exclude_thread_muter_ap_ids(activity) + notification_enabled_users = Enum.filter(potential_receivers, fn u -> u.ap_id in notification_enabled_ap_ids end) -- cgit v1.2.3 From c556efb761a3e7fc2beb4540d6f58dbfe8e4abfe Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sun, 12 Apr 2020 21:53:03 +0300 Subject: [#1364] Enabled notifications on followed domain-blocked users' activities. --- lib/pleroma/following_relationship.ex | 35 ++++++++++++++++++--- lib/pleroma/notification.ex | 59 ++++++++++++++++++++++++++++------- 2 files changed, 78 insertions(+), 16 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index a9538ea4e..11e06c5cc 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -69,6 +69,29 @@ defmodule Pleroma.FollowingRelationship do |> Repo.aggregate(:count, :id) end + def followers_query(%User{} = user) do + __MODULE__ + |> join(:inner, [r], u in User, on: r.follower_id == u.id) + |> where([r], r.following_id == ^user.id) + |> where([r], r.state == "accept") + end + + def followers_ap_ids(%User{} = user, from_ap_ids \\ nil) do + query = + user + |> followers_query() + |> select([r, u], u.ap_id) + + query = + if from_ap_ids do + where(query, [r, u], u.ap_id in ^from_ap_ids) + else + query + end + + Repo.all(query) + end + def following_count(%User{id: nil}), do: 0 def following_count(%User{} = user) do @@ -92,12 +115,16 @@ defmodule Pleroma.FollowingRelationship do |> Repo.exists?() end + def following_query(%User{} = user) do + __MODULE__ + |> join(:inner, [r], u in User, on: r.following_id == u.id) + |> where([r], r.follower_id == ^user.id) + |> where([r], r.state == "accept") + end + def following(%User{} = user) do following = - __MODULE__ - |> join(:inner, [r], u in User, on: r.following_id == u.id) - |> where([r], r.follower_id == ^user.id) - |> where([r], r.state == "accept") + following_query(user) |> select([r, u], u.follower_address) |> Repo.all() diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 02363ddb0..da05ff2e4 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Notification do use Ecto.Schema alias Pleroma.Activity + alias Pleroma.FollowingRelationship alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Pagination @@ -81,6 +82,7 @@ defmodule Pleroma.Notification do |> exclude_visibility(opts) end + # Excludes blocked users and non-followed domain-blocked users defp exclude_blocked(query, user, opts) do blocked_ap_ids = opts[:blocked_users_ap_ids] || User.blocked_users_ap_ids(user) @@ -88,7 +90,16 @@ defmodule Pleroma.Notification do |> where([n, a], a.actor not in ^blocked_ap_ids) |> where( [n, a], - fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.domain_blocks + fragment( + # "NOT (actor's domain in domain_blocks) OR (actor is in followed AP IDs)" + "NOT (substring(? from '.*://([^/]*)') = ANY(?)) OR \ + ? = ANY(SELECT ap_id FROM users AS u INNER JOIN following_relationships AS fr \ + ON u.id = fr.following_id WHERE fr.follower_id = ? AND fr.state = 'accept')", + a.actor, + ^user.domain_blocks, + a.actor, + ^User.binary_id(user.id) + ) ) end @@ -338,19 +349,11 @@ defmodule Pleroma.Notification do |> Utils.maybe_notify_followers(activity) |> Enum.uniq() - potential_receivers = - potential_receiver_ap_ids - |> Enum.uniq() - |> User.get_users_from_set(local_only) - - activity_actor_domain = activity.actor && URI.parse(activity.actor).host - - notification_enabled_ap_ids = - for u <- potential_receivers, activity_actor_domain not in u.domain_blocks, do: u.ap_id + potential_receivers = User.get_users_from_set(potential_receiver_ap_ids, local_only) - # Since even subscribers and followers can mute / thread-mute, filtering all above AP IDs notification_enabled_ap_ids = - notification_enabled_ap_ids + potential_receiver_ap_ids + |> exclude_domain_blocker_ap_ids(activity, potential_receivers) |> exclude_relationship_restricted_ap_ids(activity) |> exclude_thread_muter_ap_ids(activity) @@ -362,6 +365,38 @@ defmodule Pleroma.Notification do def get_notified_from_activity(_, _local_only), do: {[], []} + @doc "Filters out AP IDs of users who domain-block and not follow activity actor" + def exclude_domain_blocker_ap_ids(ap_ids, activity, preloaded_users \\ []) + + def exclude_domain_blocker_ap_ids([], _activity, _preloaded_users), do: [] + + def exclude_domain_blocker_ap_ids(ap_ids, %Activity{} = activity, preloaded_users) do + activity_actor_domain = activity.actor && URI.parse(activity.actor).host + + users = + ap_ids + |> Enum.map(fn ap_id -> + Enum.find(preloaded_users, &(&1.ap_id == ap_id)) || + User.get_cached_by_ap_id(ap_id) + end) + |> Enum.filter(& &1) + + domain_blocker_ap_ids = for u <- users, activity_actor_domain in u.domain_blocks, do: u.ap_id + + domain_blocker_follower_ap_ids = + if Enum.any?(domain_blocker_ap_ids) do + activity + |> Activity.user_actor() + |> FollowingRelationship.followers_ap_ids(domain_blocker_ap_ids) + else + [] + end + + ap_ids + |> Kernel.--(domain_blocker_ap_ids) + |> Kernel.++(domain_blocker_follower_ap_ids) + end + @doc "Filters out AP IDs of users basing on their relationships with activity actor user" def exclude_relationship_restricted_ap_ids([], _activity), do: [] -- cgit v1.2.3 From c5c09fc61b7b6e591e9de23028e5caea8f26b996 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Mon, 13 Apr 2020 06:53:45 +0300 Subject: fix mediaType of object --- lib/pleroma/web/activity_pub/transmogrifier.ex | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 39feae285..17e3c203a 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -35,6 +35,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> fix_actor |> fix_url |> fix_attachments + |> fix_media_type |> fix_context |> fix_in_reply_to(options) |> fix_emoji @@ -357,6 +358,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_type(object, _), do: object + defp fix_media_type(%{"mediaType" => _} = object) do + Map.put(object, "mediaType", "text/html") + end + + defp fix_media_type(object), do: object + defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do with true <- id =~ "follows", %User{local: true} = follower <- User.get_cached_by_ap_id(follower_id), @@ -1207,18 +1214,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def prepare_attachments(object) do attachments = - (object["attachment"] || []) + object + |> Map.get("attachment", []) |> Enum.map(fn data -> [%{"mediaType" => media_type, "href" => href} | _] = data["url"] - %{"url" => href, "mediaType" => media_type, "name" => data["name"], "type" => "Document"} + + %{ + "url" => href, + "mediaType" => media_type, + "name" => data["name"], + "type" => "Document" + } end) Map.put(object, "attachment", attachments) end def strip_internal_fields(object) do - object - |> Map.drop(Pleroma.Constants.object_internal_fields()) + Map.drop(object, Pleroma.Constants.object_internal_fields()) end defp strip_internal_tags(%{"tag" => tags} = object) do -- cgit v1.2.3 From 99b0bc198921099816a5f809f11a7579b3993274 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Mon, 13 Apr 2020 13:24:31 +0300 Subject: [#1364] Resolved merge conflicts with `develop`. Refactoring. --- lib/pleroma/following_relationship.ex | 34 ++++++++++++++++++++++++++++++++-- lib/pleroma/notification.ex | 14 +------------- 2 files changed, 33 insertions(+), 15 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index 219a64352..3a3082e72 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -10,11 +10,12 @@ defmodule Pleroma.FollowingRelationship do alias Ecto.Changeset alias FlakeId.Ecto.CompatType + alias Pleroma.FollowingRelationship.State alias Pleroma.Repo alias Pleroma.User schema "following_relationships" do - field(:state, Pleroma.FollowingRelationship.State, default: :follow_pending) + field(:state, State, default: :follow_pending) belongs_to(:follower, User, type: CompatType) belongs_to(:following, User, type: CompatType) @@ -22,6 +23,11 @@ defmodule Pleroma.FollowingRelationship do timestamps() end + @doc "Returns underlying integer code for state atom" + def state_int_code(state_atom), do: State.__enum_map__() |> Keyword.fetch!(state_atom) + + def accept_state_code, do: state_int_code(:follow_accept) + def changeset(%__MODULE__{} = following_relationship, attrs) do following_relationship |> cast(attrs, [:state]) @@ -86,7 +92,7 @@ defmodule Pleroma.FollowingRelationship do __MODULE__ |> join(:inner, [r], u in User, on: r.follower_id == u.id) |> where([r], r.following_id == ^user.id) - |> where([r], r.state == "accept") + |> where([r], r.state == ^:follow_accept) end def followers_ap_ids(%User{} = user, from_ap_ids \\ nil) do @@ -198,6 +204,30 @@ defmodule Pleroma.FollowingRelationship do end) end + @doc """ + For a query with joined activity, + keeps rows where activity's actor is followed by user -or- is NOT domain-blocked by user. + """ + def keep_following_or_not_domain_blocked(query, user) do + where( + query, + [_, activity], + fragment( + # "(actor's domain NOT in domain_blocks) OR (actor IS in followed AP IDs)" + """ + NOT (substring(? from '.*://([^/]*)') = ANY(?)) OR + ? = ANY(SELECT ap_id FROM users AS u INNER JOIN following_relationships AS fr + ON u.id = fr.following_id WHERE fr.follower_id = ? AND fr.state = ?) + """, + activity.actor, + ^user.domain_blocks, + activity.actor, + ^User.binary_id(user.id), + ^accept_state_code() + ) + ) + end + defp validate_not_self_relationship(%Changeset{} = changeset) do changeset |> validate_follower_id_following_id_inequality() diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index da05ff2e4..b76dd176c 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -88,19 +88,7 @@ defmodule Pleroma.Notification do query |> where([n, a], a.actor not in ^blocked_ap_ids) - |> where( - [n, a], - fragment( - # "NOT (actor's domain in domain_blocks) OR (actor is in followed AP IDs)" - "NOT (substring(? from '.*://([^/]*)') = ANY(?)) OR \ - ? = ANY(SELECT ap_id FROM users AS u INNER JOIN following_relationships AS fr \ - ON u.id = fr.following_id WHERE fr.follower_id = ? AND fr.state = 'accept')", - a.actor, - ^user.domain_blocks, - a.actor, - ^User.binary_id(user.id) - ) - ) + |> FollowingRelationship.keep_following_or_not_domain_blocked(user) end defp exclude_notification_muted(query, _, %{@include_muted_option => true}) do -- cgit v1.2.3 From b08ded6c2f5ee29c6efc8c67cfc2ce0a679f0c77 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Fri, 3 Apr 2020 22:45:08 +0400 Subject: Add spec for AccountController.create --- .../web/api_spec/operations/account_operation.ex | 68 +++++++++++++ lib/pleroma/web/api_spec/render_error.ex | 27 ++++++ .../web/api_spec/schemas/account_create_request.ex | 56 +++++++++++ .../api_spec/schemas/account_create_response.ex | 29 ++++++ .../mastodon_api/controllers/account_controller.ex | 36 ++++--- lib/pleroma/web/twitter_api/twitter_api.ex | 106 +++++++++------------ 6 files changed, 246 insertions(+), 76 deletions(-) create mode 100644 lib/pleroma/web/api_spec/operations/account_operation.ex create mode 100644 lib/pleroma/web/api_spec/render_error.ex create mode 100644 lib/pleroma/web/api_spec/schemas/account_create_request.ex create mode 100644 lib/pleroma/web/api_spec/schemas/account_create_response.ex (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex new file mode 100644 index 000000000..9085f1af1 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -0,0 +1,68 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.AccountOperation do + alias OpenApiSpex.Operation + alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest + alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse + alias Pleroma.Web.ApiSpec.Helpers + + @spec open_api_operation(atom) :: Operation.t() + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + @spec create_operation() :: Operation.t() + def create_operation do + %Operation{ + tags: ["accounts"], + summary: "Register an account", + description: + "Creates a user and account records. Returns an account access token for the app that initiated the request. The app should save this token for later, and should wait for the user to confirm their account by clicking a link in their email inbox.", + operationId: "AccountController.create", + requestBody: Helpers.request_body("Parameters", AccountCreateRequest, required: true), + responses: %{ + 200 => Operation.response("Account", "application/json", AccountCreateResponse) + } + } + end + + def verify_credentials_operation do + :ok + end + + def update_credentials_operation do + :ok + end + + def relationships_operation do + :ok + end + + def show_operation do + :ok + end + + def statuses_operation do + :ok + end + + def followers_operation do + :ok + end + + def following_operation, do: :ok + def lists_operation, do: :ok + def follow_operation, do: :ok + def unfollow_operation, do: :ok + def mute_operation, do: :ok + def unmute_operation, do: :ok + def block_operation, do: :ok + def unblock_operation, do: :ok + def follows_operation, do: :ok + def mutes_operation, do: :ok + def blocks_operation, do: :ok + def endorsements_operation, do: :ok +end diff --git a/lib/pleroma/web/api_spec/render_error.ex b/lib/pleroma/web/api_spec/render_error.ex new file mode 100644 index 000000000..e063d115b --- /dev/null +++ b/lib/pleroma/web/api_spec/render_error.ex @@ -0,0 +1,27 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.RenderError do + @behaviour Plug + + alias Plug.Conn + alias OpenApiSpex.Plug.JsonRenderError + + @impl Plug + def init(opts), do: opts + + @impl Plug + + def call(%{private: %{open_api_spex: %{operation_id: "AccountController.create"}}} = conn, _) do + conn + |> Conn.put_status(:bad_request) + |> Phoenix.Controller.json(%{"error" => "Missing parameters"}) + end + + def call(conn, reason) do + opts = JsonRenderError.init(reason) + + JsonRenderError.call(conn, opts) + end +end diff --git a/lib/pleroma/web/api_spec/schemas/account_create_request.ex b/lib/pleroma/web/api_spec/schemas/account_create_request.ex new file mode 100644 index 000000000..398e2d613 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/account_create_request.ex @@ -0,0 +1,56 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest do + alias OpenApiSpex.Schema + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "AccountCreateRequest", + description: "POST body for creating an account", + type: :object, + properties: %{ + reason: %Schema{ + type: :string, + description: + "Text that will be reviewed by moderators if registrations require manual approval" + }, + username: %Schema{type: :string, description: "The desired username for the account"}, + email: %Schema{ + type: :string, + description: + "The email address to be used for login. Required when `account_activation_required` is enabled.", + format: :email + }, + password: %Schema{type: :string, description: "The password to be used for login"}, + agreement: %Schema{ + type: :boolean, + description: + "Whether the user agrees to the local rules, terms, and policies. These should be presented to the user in order to allow them to consent before setting this parameter to TRUE." + }, + locale: %Schema{ + type: :string, + description: "The language of the confirmation email that will be sent" + }, + # Pleroma-specific properties: + fullname: %Schema{type: :string, description: "Full name"}, + bio: %Schema{type: :string, description: "Bio", default: ""}, + captcha_solution: %Schema{type: :string, description: "Provider-specific captcha solution"}, + captcha_token: %Schema{type: :string, description: "Provider-specific captcha token"}, + captcha_answer_data: %Schema{type: :string, description: "Provider-specific captcha data"}, + token: %Schema{ + type: :string, + description: "Invite token required when the registrations aren't public" + } + }, + required: [:username, :password, :agreement], + example: %{ + "username" => "cofe", + "email" => "cofe@example.com", + "password" => "secret", + "agreement" => "true", + "bio" => "☕️" + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/account_create_response.ex b/lib/pleroma/web/api_spec/schemas/account_create_response.ex new file mode 100644 index 000000000..f41a034c0 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/account_create_response.ex @@ -0,0 +1,29 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse do + alias OpenApiSpex.Schema + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "AccountCreateResponse", + description: "Response schema for an account", + type: :object, + properties: %{ + token_type: %Schema{type: :string}, + access_token: %Schema{type: :string}, + scope: %Schema{type: :array, items: %Schema{type: :string}}, + created_at: %Schema{type: :integer} + }, + example: %{ + "JSON" => %{ + "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk", + "created_at" => 1_585_918_714, + "scope" => ["read", "write", "follow", "push"], + "token_type" => "Bearer" + } + } + }) +end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 7da1a11f6..eb082daf8 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -80,27 +80,33 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug(RateLimiter, [name: :app_account_creation] when action == :create) plug(:assign_account_by_id when action in @needs_account) + plug( + OpenApiSpex.Plug.CastAndValidate, + [render_error: Pleroma.Web.ApiSpec.RenderError] when action == :create + ) + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AccountOperation + @doc "POST /api/v1/accounts" - def create( - %{assigns: %{app: app}} = conn, - %{"username" => nickname, "password" => _, "agreement" => true} = params - ) do + def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do params = params |> Map.take([ - "email", - "captcha_solution", - "captcha_token", - "captcha_answer_data", - "token", - "password" + :email, + :bio, + :captcha_solution, + :captcha_token, + :captcha_answer_data, + :token, + :password, + :fullname ]) - |> Map.put("nickname", nickname) - |> Map.put("fullname", params["fullname"] || nickname) - |> Map.put("bio", params["bio"] || "") - |> Map.put("confirm", params["password"]) + |> Map.put(:nickname, params.username) + |> Map.put(:fullname, params.fullname || params.username) + |> Map.put(:bio, params.bio || "") + |> Map.put(:confirm, params.password) with :ok <- validate_email_param(params), {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true), @@ -124,7 +130,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do render_error(conn, :forbidden, "Invalid credentials") end - defp validate_email_param(%{"email" => _}), do: :ok + defp validate_email_param(%{:email => email}) when not is_nil(email), do: :ok defp validate_email_param(_) do case Pleroma.Config.get([:instance, :account_activation_required]) do diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index f9c0994da..37be48b5a 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -12,72 +12,56 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do require Pleroma.Constants def register_user(params, opts \\ []) do - token = params["token"] - - params = %{ - nickname: params["nickname"], - name: params["fullname"], - bio: User.parse_bio(params["bio"]), - email: params["email"], - password: params["password"], - password_confirmation: params["confirm"], - captcha_solution: params["captcha_solution"], - captcha_token: params["captcha_token"], - captcha_answer_data: params["captcha_answer_data"] - } - - 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 - :ok - else - Pleroma.Captcha.validate( - params[:captcha_token], - params[:captcha_solution], - params[:captcha_answer_data] - ) - end - - # Captcha invalid - if captcha_ok != :ok do - {:error, error} = captcha_ok - # I have no idea how this error handling works - {:error, %{error: Jason.encode!(%{captcha: [error]})}} - else - registration_process( - params, - %{ - registrations_open: Pleroma.Config.get([:instance, :registrations_open]), - token: token - }, - opts - ) + params = + params + |> Map.take([ + :nickname, + :password, + :captcha_solution, + :captcha_token, + :captcha_answer_data, + :token, + :email + ]) + |> Map.put(:bio, User.parse_bio(params[:bio] || "")) + |> Map.put(:name, params.fullname) + |> Map.put(:password_confirmation, params[:confirm]) + + case validate_captcha(params) do + :ok -> + if Pleroma.Config.get([:instance, :registrations_open]) do + create_user(params, opts) + else + create_user_with_invite(params, opts) + end + + {:error, error} -> + # I have no idea how this error handling works + {:error, %{error: Jason.encode!(%{captcha: [error]})}} end end - defp registration_process(params, %{registrations_open: true}, opts) do - create_user(params, opts) + defp validate_captcha(params) do + if Pleroma.Config.get([Pleroma.Captcha, :enabled]) do + Pleroma.Captcha.validate( + params.captcha_token, + params.captcha_solution, + params.captcha_answer_data + ) + else + :ok + end end - defp registration_process(params, %{token: token}, opts) do - invite = - unless is_nil(token) do - Repo.get_by(UserInviteToken, %{token: token}) - end - - valid_invite? = invite && UserInviteToken.valid_invite?(invite) - - case invite do - nil -> - {:error, "Invalid token"} - - invite when valid_invite? -> - UserInviteToken.update_usage!(invite) - create_user(params, opts) - - _ -> - {:error, "Expired token"} + defp create_user_with_invite(params, opts) do + with %{token: token} when is_binary(token) <- params, + %UserInviteToken{} = invite <- Repo.get_by(UserInviteToken, %{token: token}), + true <- UserInviteToken.valid_invite?(invite) do + UserInviteToken.update_usage!(invite) + create_user(params, opts) + else + nil -> {:error, "Invalid token"} + _ -> {:error, "Expired token"} end end -- cgit v1.2.3 From f80116125f928de36c93627bbdf5f6578396f53b Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 6 Apr 2020 00:15:37 +0400 Subject: Add spec for AccountController.verify_credentials --- lib/pleroma/web/api_spec.ex | 2 +- .../web/api_spec/operations/account_operation.ex | 14 +- .../web/api_spec/operations/app_operation.ex | 6 +- lib/pleroma/web/api_spec/render_error.ex | 2 +- lib/pleroma/web/api_spec/schemas/account.ex | 181 +++++++++++++++++++++ lib/pleroma/web/api_spec/schemas/account_emoji.ex | 31 ++++ lib/pleroma/web/api_spec/schemas/account_field.ex | 28 ++++ 7 files changed, 255 insertions(+), 9 deletions(-) create mode 100644 lib/pleroma/web/api_spec/schemas/account.ex create mode 100644 lib/pleroma/web/api_spec/schemas/account_emoji.ex create mode 100644 lib/pleroma/web/api_spec/schemas/account_field.ex (limited to 'lib') diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex index 41e48a085..c85fe30d1 100644 --- a/lib/pleroma/web/api_spec.ex +++ b/lib/pleroma/web/api_spec.ex @@ -31,7 +31,7 @@ defmodule Pleroma.Web.ApiSpec do password: %OpenApiSpex.OAuthFlow{ authorizationUrl: "/oauth/authorize", tokenUrl: "/oauth/token", - scopes: %{"read" => "read"} + scopes: %{"read" => "read", "write" => "write"} } } } diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 9085f1af1..3d2270c29 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -4,9 +4,10 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do alias OpenApiSpex.Operation + alias Pleroma.Web.ApiSpec.Helpers + alias Pleroma.Web.ApiSpec.Schemas.Account alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse - alias Pleroma.Web.ApiSpec.Helpers @spec open_api_operation(atom) :: Operation.t() def open_api_operation(action) do @@ -30,7 +31,16 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do end def verify_credentials_operation do - :ok + %Operation{ + tags: ["accounts"], + description: "Test to make sure that the user token works.", + summary: "Verify account credentials", + operationId: "AccountController.verify_credentials", + security: [%{"oAuth" => ["read:accounts"]}], + responses: %{ + 200 => Operation.response("Account", "application/json", Account) + } + } end def update_credentials_operation do diff --git a/lib/pleroma/web/api_spec/operations/app_operation.ex b/lib/pleroma/web/api_spec/operations/app_operation.ex index 26d8dbd42..935215c64 100644 --- a/lib/pleroma/web/api_spec/operations/app_operation.ex +++ b/lib/pleroma/web/api_spec/operations/app_operation.ex @@ -51,11 +51,7 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do summary: "Verify your app works", description: "Confirm that the app's OAuth2 credentials work.", operationId: "AppController.verify_credentials", - security: [ - %{ - "oAuth" => ["read"] - } - ], + security: [%{"oAuth" => ["read"]}], responses: %{ 200 => Operation.response("App", "application/json", %Schema{ diff --git a/lib/pleroma/web/api_spec/render_error.ex b/lib/pleroma/web/api_spec/render_error.ex index e063d115b..9184c43b6 100644 --- a/lib/pleroma/web/api_spec/render_error.ex +++ b/lib/pleroma/web/api_spec/render_error.ex @@ -5,8 +5,8 @@ defmodule Pleroma.Web.ApiSpec.RenderError do @behaviour Plug - alias Plug.Conn alias OpenApiSpex.Plug.JsonRenderError + alias Plug.Conn @impl Plug def init(opts), do: opts diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex new file mode 100644 index 000000000..59c4ac4a4 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -0,0 +1,181 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.Account do + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji + alias Pleroma.Web.ApiSpec.Schemas.AccountField + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "Account", + description: "Response schema for an account", + type: :object, + properties: %{ + acct: %Schema{type: :string}, + avatar_static: %Schema{type: :string}, + avatar: %Schema{type: :string}, + bot: %Schema{type: :boolean}, + created_at: %Schema{type: :string, format: "date-time"}, + display_name: %Schema{type: :string}, + emojis: %Schema{type: :array, items: AccountEmoji}, + fields: %Schema{type: :array, items: AccountField}, + follow_requests_count: %Schema{type: :integer}, + followers_count: %Schema{type: :integer}, + following_count: %Schema{type: :integer}, + header_static: %Schema{type: :string}, + header: %Schema{type: :string}, + id: %Schema{type: :string}, + locked: %Schema{type: :boolean}, + note: %Schema{type: :string}, + statuses_count: %Schema{type: :integer}, + url: %Schema{type: :string}, + username: %Schema{type: :string}, + pleroma: %Schema{ + type: :object, + properties: %{ + allow_following_move: %Schema{type: :boolean}, + background_image: %Schema{type: :boolean, nullable: true}, + chat_token: %Schema{type: :string}, + confirmation_pending: %Schema{type: :boolean}, + hide_favorites: %Schema{type: :boolean}, + hide_followers_count: %Schema{type: :boolean}, + hide_followers: %Schema{type: :boolean}, + hide_follows_count: %Schema{type: :boolean}, + hide_follows: %Schema{type: :boolean}, + is_admin: %Schema{type: :boolean}, + is_moderator: %Schema{type: :boolean}, + skip_thread_containment: %Schema{type: :boolean}, + tags: %Schema{type: :array, items: %Schema{type: :string}}, + unread_conversation_count: %Schema{type: :integer}, + notification_settings: %Schema{ + type: :object, + properties: %{ + followers: %Schema{type: :boolean}, + follows: %Schema{type: :boolean}, + non_followers: %Schema{type: :boolean}, + non_follows: %Schema{type: :boolean}, + privacy_option: %Schema{type: :boolean} + } + }, + relationship: %Schema{ + type: :object, + properties: %{ + blocked_by: %Schema{type: :boolean}, + blocking: %Schema{type: :boolean}, + domain_blocking: %Schema{type: :boolean}, + endorsed: %Schema{type: :boolean}, + followed_by: %Schema{type: :boolean}, + following: %Schema{type: :boolean}, + id: %Schema{type: :string}, + muting: %Schema{type: :boolean}, + muting_notifications: %Schema{type: :boolean}, + requested: %Schema{type: :boolean}, + showing_reblogs: %Schema{type: :boolean}, + subscribing: %Schema{type: :boolean} + } + }, + settings_store: %Schema{ + type: :object + } + } + }, + source: %Schema{ + type: :object, + properties: %{ + fields: %Schema{type: :array, items: AccountField}, + note: %Schema{type: :string}, + privacy: %Schema{type: :string}, + sensitive: %Schema{type: :boolean}, + pleroma: %Schema{ + type: :object, + properties: %{ + actor_type: %Schema{type: :string}, + discoverable: %Schema{type: :boolean}, + no_rich_text: %Schema{type: :boolean}, + show_role: %Schema{type: :boolean} + } + } + } + } + }, + example: %{ + "JSON" => %{ + "acct" => "foobar", + "avatar" => "https://mypleroma.com/images/avi.png", + "avatar_static" => "https://mypleroma.com/images/avi.png", + "bot" => false, + "created_at" => "2020-03-24T13:05:58.000Z", + "display_name" => "foobar", + "emojis" => [], + "fields" => [], + "follow_requests_count" => 0, + "followers_count" => 0, + "following_count" => 1, + "header" => "https://mypleroma.com/images/banner.png", + "header_static" => "https://mypleroma.com/images/banner.png", + "id" => "9tKi3esbG7OQgZ2920", + "locked" => false, + "note" => "cofe", + "pleroma" => %{ + "allow_following_move" => true, + "background_image" => nil, + "confirmation_pending" => true, + "hide_favorites" => true, + "hide_followers" => false, + "hide_followers_count" => false, + "hide_follows" => false, + "hide_follows_count" => false, + "is_admin" => false, + "is_moderator" => false, + "skip_thread_containment" => false, + "chat_token" => + "SFMyNTY.g3QAAAACZAAEZGF0YW0AAAASOXRLaTNlc2JHN09RZ1oyOTIwZAAGc2lnbmVkbgYARNplS3EB.Mb_Iaqew2bN1I1o79B_iP7encmVCpTKC4OtHZRxdjKc", + "unread_conversation_count" => 0, + "tags" => [], + "notification_settings" => %{ + "followers" => true, + "follows" => true, + "non_followers" => true, + "non_follows" => true, + "privacy_option" => false + }, + "relationship" => %{ + "blocked_by" => false, + "blocking" => false, + "domain_blocking" => false, + "endorsed" => false, + "followed_by" => false, + "following" => false, + "id" => "9tKi3esbG7OQgZ2920", + "muting" => false, + "muting_notifications" => false, + "requested" => false, + "showing_reblogs" => true, + "subscribing" => false + }, + "settings_store" => %{ + "pleroma-fe" => %{} + } + }, + "source" => %{ + "fields" => [], + "note" => "foobar", + "pleroma" => %{ + "actor_type" => "Person", + "discoverable" => false, + "no_rich_text" => false, + "show_role" => true + }, + "privacy" => "public", + "sensitive" => false + }, + "statuses_count" => 0, + "url" => "https://mypleroma.com/users/foobar", + "username" => "foobar" + } + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/account_emoji.ex b/lib/pleroma/web/api_spec/schemas/account_emoji.ex new file mode 100644 index 000000000..403b13b15 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/account_emoji.ex @@ -0,0 +1,31 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.AccountEmoji do + alias OpenApiSpex.Schema + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "AccountEmoji", + description: "Response schema for account custom fields", + type: :object, + properties: %{ + shortcode: %Schema{type: :string}, + url: %Schema{type: :string}, + static_url: %Schema{type: :string}, + visible_in_picker: %Schema{type: :boolean} + }, + example: %{ + "JSON" => %{ + "shortcode" => "fatyoshi", + "url" => + "https://files.mastodon.social/custom_emojis/images/000/023/920/original/e57ecb623faa0dc9.png", + "static_url" => + "https://files.mastodon.social/custom_emojis/images/000/023/920/static/e57ecb623faa0dc9.png", + "visible_in_picker" => true + } + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/account_field.ex b/lib/pleroma/web/api_spec/schemas/account_field.ex new file mode 100644 index 000000000..8906d812d --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/account_field.ex @@ -0,0 +1,28 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.AccountField do + alias OpenApiSpex.Schema + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "AccountField", + description: "Response schema for account custom fields", + type: :object, + properties: %{ + name: %Schema{type: :string}, + value: %Schema{type: :string}, + verified_at: %Schema{type: :string, format: "date-time", nullable: true} + }, + example: %{ + "JSON" => %{ + "name" => "Website", + "value" => + "https://pleroma.com", + "verified_at" => "2019-08-29T04:14:55.571+00:00" + } + } + }) +end -- cgit v1.2.3 From 260cbddc943e53a85762e56852de65d2b900cc04 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 7 Apr 2020 14:53:12 +0400 Subject: Add spec for AccountController.update_credentials --- lib/pleroma/web/api_spec/helpers.ex | 2 +- .../web/api_spec/operations/account_operation.ex | 14 ++- .../api_spec/schemas/account_field_attribute.ex | 26 +++++ .../schemas/account_update_credentials_request.ex | 123 +++++++++++++++++++++ .../mastodon_api/controllers/account_controller.ex | 41 ++++--- 5 files changed, 190 insertions(+), 16 deletions(-) create mode 100644 lib/pleroma/web/api_spec/schemas/account_field_attribute.ex create mode 100644 lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex index 35cf4c0d8..7348dcbee 100644 --- a/lib/pleroma/web/api_spec/helpers.ex +++ b/lib/pleroma/web/api_spec/helpers.ex @@ -4,7 +4,7 @@ defmodule Pleroma.Web.ApiSpec.Helpers do def request_body(description, schema_ref, opts \\ []) do - media_types = ["application/json", "multipart/form-data"] + media_types = ["application/json", "multipart/form-data", "application/x-www-form-urlencoded"] content = media_types diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 3d2270c29..d7b56cc2b 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do alias Pleroma.Web.ApiSpec.Schemas.Account alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse + alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest @spec open_api_operation(atom) :: Operation.t() def open_api_operation(action) do @@ -44,7 +45,18 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do end def update_credentials_operation do - :ok + %Operation{ + tags: ["accounts"], + summary: "Update account credentials", + description: "Update the user's display and preferences.", + operationId: "AccountController.update_credentials", + security: [%{"oAuth" => ["write:accounts"]}], + requestBody: + Helpers.request_body("Parameters", AccountUpdateCredentialsRequest, required: true), + responses: %{ + 200 => Operation.response("Account", "application/json", Account) + } + } end def relationships_operation do diff --git a/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex b/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex new file mode 100644 index 000000000..fbbdf95f5 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex @@ -0,0 +1,26 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.AccountAttributeField do + alias OpenApiSpex.Schema + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "AccountAttributeField", + description: "Request schema for account custom fields", + type: :object, + properties: %{ + name: %Schema{type: :string}, + value: %Schema{type: :string} + }, + required: [:name, :value], + example: %{ + "JSON" => %{ + "name" => "Website", + "value" => "https://pleroma.com" + } + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex b/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex new file mode 100644 index 000000000..a50bce5ed --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex @@ -0,0 +1,123 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest do + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.AccountAttributeField + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "AccountUpdateCredentialsRequest", + description: "POST body for creating an account", + type: :object, + properties: %{ + bot: %Schema{ + type: :boolean, + description: "Whether the account has a bot flag." + }, + display_name: %Schema{ + type: :string, + description: "The display name to use for the profile." + }, + note: %Schema{type: :string, description: "The account bio."}, + avatar: %Schema{ + type: :string, + description: "Avatar image encoded using multipart/form-data", + format: :binary + }, + header: %Schema{ + type: :string, + description: "Header image encoded using multipart/form-data", + format: :binary + }, + locked: %Schema{ + type: :boolean, + description: "Whether manual approval of follow requests is required." + }, + fields_attributes: %Schema{ + oneOf: [%Schema{type: :array, items: AccountAttributeField}, %Schema{type: :object}] + }, + # NOTE: `source` field is not supported + # + # source: %Schema{ + # type: :object, + # properties: %{ + # privacy: %Schema{type: :string}, + # sensitive: %Schema{type: :boolean}, + # language: %Schema{type: :string} + # } + # }, + + # Pleroma-specific fields + no_rich_text: %Schema{ + type: :boolean, + description: "html tags are stripped from all statuses requested from the API" + }, + hide_followers: %Schema{type: :boolean, description: "user's followers will be hidden"}, + hide_follows: %Schema{type: :boolean, description: "user's follows will be hidden"}, + hide_followers_count: %Schema{ + type: :boolean, + description: "user's follower count will be hidden" + }, + hide_follows_count: %Schema{ + type: :boolean, + description: "user's follow count will be hidden" + }, + hide_favorites: %Schema{ + type: :boolean, + description: "user's favorites timeline will be hidden" + }, + show_role: %Schema{ + type: :boolean, + description: "user's role (e.g admin, moderator) will be exposed to anyone in the + API" + }, + default_scope: %Schema{ + type: :string, + description: "The scope returned under privacy key in Source subentity" + }, + pleroma_settings_store: %Schema{ + type: :object, + description: "Opaque user settings to be saved on the backend." + }, + skip_thread_containment: %Schema{ + type: :boolean, + description: "Skip filtering out broken threads" + }, + allow_following_move: %Schema{ + type: :boolean, + description: "Allows automatically follow moved following accounts" + }, + pleroma_background_image: %Schema{ + type: :string, + description: "Sets the background image of the user.", + format: :binary + }, + discoverable: %Schema{ + type: :boolean, + description: "Discovery of this account in search results and other services is allowed." + }, + actor_type: %Schema{type: :string, description: "the type of this account."} + }, + example: %{ + bot: false, + display_name: "cofe", + note: "foobar", + fields_attributes: [%{name: "foo", value: "bar"}], + no_rich_text: false, + hide_followers: true, + hide_follows: false, + hide_followers_count: false, + hide_follows_count: false, + hide_favorites: false, + show_role: false, + default_scope: "private", + pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}}, + skip_thread_containment: false, + allow_following_move: false, + discoverable: false, + actor_type: "Person" + } + }) +end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index eb082daf8..9c986b3b2 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -82,7 +82,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug( OpenApiSpex.Plug.CastAndValidate, - [render_error: Pleroma.Web.ApiSpec.RenderError] when action == :create + [render_error: Pleroma.Web.ApiSpec.RenderError] + when action in [:create, :verify_credentials, :update_credentials] ) action_fallback(Pleroma.Web.MastodonAPI.FallbackController) @@ -152,9 +153,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "PATCH /api/v1/accounts/update_credentials" - def update_credentials(%{assigns: %{user: original_user}} = conn, params) do + def update_credentials(%{assigns: %{user: original_user}, body_params: params} = conn, _params) do user = original_user + params = + params + |> Map.from_struct() + |> Enum.filter(fn {_, value} -> not is_nil(value) end) + |> Enum.into(%{}) + user_params = [ :no_rich_text, @@ -170,22 +177,22 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do :discoverable ] |> Enum.reduce(%{}, fn key, acc -> - add_if_present(acc, params, to_string(key), key, &{:ok, truthy_param?(&1)}) + add_if_present(acc, params, key, key, &{:ok, truthy_param?(&1)}) end) - |> add_if_present(params, "display_name", :name) - |> add_if_present(params, "note", :bio) - |> add_if_present(params, "avatar", :avatar) - |> add_if_present(params, "header", :banner) - |> add_if_present(params, "pleroma_background_image", :background) + |> add_if_present(params, :display_name, :name) + |> add_if_present(params, :note, :bio) + |> add_if_present(params, :avatar, :avatar) + |> add_if_present(params, :header, :banner) + |> add_if_present(params, :pleroma_background_image, :background) |> add_if_present( params, - "fields_attributes", + :fields_attributes, :raw_fields, &{:ok, normalize_fields_attributes(&1)} ) - |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store) - |> add_if_present(params, "default_scope", :default_scope) - |> add_if_present(params, "actor_type", :actor_type) + |> add_if_present(params, :pleroma_settings_store, :pleroma_settings_store) + |> add_if_present(params, :default_scope, :default_scope) + |> add_if_present(params, :actor_type, :actor_type) changeset = User.update_changeset(user, user_params) @@ -200,7 +207,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do defp add_if_present(map, params, params_field, map_field, value_function \\ &{:ok, &1}) do with true <- Map.has_key?(params, params_field), - {:ok, new_value} <- value_function.(params[params_field]) do + {:ok, new_value} <- value_function.(Map.get(params, params_field)) do Map.put(map, map_field, new_value) else _ -> map @@ -211,7 +218,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do if Enum.all?(fields, &is_tuple/1) do Enum.map(fields, fn {_, v} -> v end) else - fields + Enum.map(fields, fn + %Pleroma.Web.ApiSpec.Schemas.AccountAttributeField{} = field -> + %{"name" => field.name, "value" => field.value} + + field -> + field + end) end end -- cgit v1.2.3 From ab400b2ddb205271b0a2680c45db18844f59a27d Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 7 Apr 2020 16:18:23 +0400 Subject: Add specs for ActorType and VisibilityScope --- lib/pleroma/web/api_spec/schemas/account.ex | 6 ++++-- .../api_spec/schemas/account_update_credentials_request.ex | 9 ++++----- lib/pleroma/web/api_spec/schemas/actor_type.ex | 13 +++++++++++++ lib/pleroma/web/api_spec/schemas/visibility_scope.ex | 14 ++++++++++++++ 4 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 lib/pleroma/web/api_spec/schemas/actor_type.ex create mode 100644 lib/pleroma/web/api_spec/schemas/visibility_scope.ex (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index 59c4ac4a4..beb093182 100644 --- a/lib/pleroma/web/api_spec/schemas/account.ex +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -6,6 +6,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji alias Pleroma.Web.ApiSpec.Schemas.AccountField + alias Pleroma.Web.ApiSpec.Schemas.ActorType + alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope require OpenApiSpex @@ -87,12 +89,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do properties: %{ fields: %Schema{type: :array, items: AccountField}, note: %Schema{type: :string}, - privacy: %Schema{type: :string}, + privacy: VisibilityScope, sensitive: %Schema{type: :boolean}, pleroma: %Schema{ type: :object, properties: %{ - actor_type: %Schema{type: :string}, + actor_type: ActorType, discoverable: %Schema{type: :boolean}, no_rich_text: %Schema{type: :boolean}, show_role: %Schema{type: :boolean} diff --git a/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex b/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex index a50bce5ed..6ab48193e 100644 --- a/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex +++ b/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex @@ -5,6 +5,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest do alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Schemas.AccountAttributeField + alias Pleroma.Web.ApiSpec.Schemas.ActorType + alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope require OpenApiSpex OpenApiSpex.schema(%{ @@ -73,10 +75,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest do description: "user's role (e.g admin, moderator) will be exposed to anyone in the API" }, - default_scope: %Schema{ - type: :string, - description: "The scope returned under privacy key in Source subentity" - }, + default_scope: VisibilityScope, pleroma_settings_store: %Schema{ type: :object, description: "Opaque user settings to be saved on the backend." @@ -98,7 +97,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest do type: :boolean, description: "Discovery of this account in search results and other services is allowed." }, - actor_type: %Schema{type: :string, description: "the type of this account."} + actor_type: ActorType }, example: %{ bot: false, diff --git a/lib/pleroma/web/api_spec/schemas/actor_type.ex b/lib/pleroma/web/api_spec/schemas/actor_type.ex new file mode 100644 index 000000000..ac9b46678 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/actor_type.ex @@ -0,0 +1,13 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.ActorType do + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "ActorType", + type: :string, + enum: ["Application", "Group", "Organization", "Person", "Service"] + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/visibility_scope.ex b/lib/pleroma/web/api_spec/schemas/visibility_scope.ex new file mode 100644 index 000000000..8c81a4d73 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/visibility_scope.ex @@ -0,0 +1,14 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.VisibilityScope do + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "VisibilityScope", + description: "Status visibility", + type: :string, + enum: ["public", "unlisted", "private", "direct"] + }) +end -- cgit v1.2.3 From d7d6a83233f24b80005b4f49a8697535620e4b83 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 7 Apr 2020 18:29:05 +0400 Subject: Add spec for AccountController.relationships --- .../web/api_spec/operations/account_operation.ex | 24 +++++++++- .../schemas/account_relationship_response.ex | 43 +++++++++++++++++ .../schemas/account_relationships_response.ex | 55 ++++++++++++++++++++++ .../mastodon_api/controllers/account_controller.ex | 4 +- 4 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 lib/pleroma/web/api_spec/schemas/account_relationship_response.ex create mode 100644 lib/pleroma/web/api_spec/schemas/account_relationships_response.ex (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index d7b56cc2b..352f66e9d 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -4,10 +4,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do alias OpenApiSpex.Operation + alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Helpers alias Pleroma.Web.ApiSpec.Schemas.Account alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse + alias Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest @spec open_api_operation(atom) :: Operation.t() @@ -60,7 +62,27 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do end def relationships_operation do - :ok + %Operation{ + tags: ["accounts"], + summary: "Check relationships to other accounts", + operationId: "AccountController.relationships", + description: "Find out whether a given account is followed, blocked, muted, etc.", + security: [%{"oAuth" => ["read:follows"]}], + parameters: [ + Operation.parameter( + :id, + :query, + %Schema{ + oneOf: [%Schema{type: :array, items: %Schema{type: :string}}, %Schema{type: :string}] + }, + "Account IDs", + example: "123" + ) + ], + responses: %{ + 200 => Operation.response("Account", "application/json", AccountRelationshipsResponse) + } + } end def show_operation do diff --git a/lib/pleroma/web/api_spec/schemas/account_relationship_response.ex b/lib/pleroma/web/api_spec/schemas/account_relationship_response.ex new file mode 100644 index 000000000..9974b946b --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/account_relationship_response.ex @@ -0,0 +1,43 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationshipResponse do + alias OpenApiSpex.Schema + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "AccountRelationshipResponse", + description: "Response schema for an account relationship", + type: :object, + properties: %{ + id: %Schema{type: :string}, + following: %Schema{type: :boolean}, + showing_reblogs: %Schema{type: :boolean}, + followed_by: %Schema{type: :boolean}, + blocking: %Schema{type: :boolean}, + blocked_by: %Schema{type: :boolean}, + muting: %Schema{type: :boolean}, + muting_notifications: %Schema{type: :boolean}, + requested: %Schema{type: :boolean}, + domain_blocking: %Schema{type: :boolean}, + endorsed: %Schema{type: :boolean} + }, + example: %{ + "JSON" => %{ + "id" => "1", + "following" => true, + "showing_reblogs" => true, + "followed_by" => true, + "blocking" => false, + "blocked_by" => false, + "muting" => false, + "muting_notifications" => false, + "requested" => false, + "domain_blocking" => false, + "endorsed" => false + } + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex b/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex new file mode 100644 index 000000000..2ca632310 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex @@ -0,0 +1,55 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse do + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "AccountRelationshipsResponse", + description: "Response schema for account relationships", + type: :array, + items: Pleroma.Web.ApiSpec.Schemas.AccountRelationshipResponse, + example: [ + %{ + "id" => "1", + "following" => true, + "showing_reblogs" => true, + "followed_by" => true, + "blocking" => false, + "blocked_by" => true, + "muting" => false, + "muting_notifications" => false, + "requested" => false, + "domain_blocking" => false, + "endorsed" => true + }, + %{ + "id" => "2", + "following" => true, + "showing_reblogs" => true, + "followed_by" => true, + "blocking" => false, + "blocked_by" => true, + "muting" => true, + "muting_notifications" => false, + "requested" => true, + "domain_blocking" => false, + "endorsed" => false + }, + %{ + "id" => "3", + "following" => true, + "showing_reblogs" => true, + "followed_by" => true, + "blocking" => true, + "blocked_by" => false, + "muting" => true, + "muting_notifications" => false, + "requested" => false, + "domain_blocking" => true, + "endorsed" => false + } + ] + }) +end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 9c986b3b2..1652e3a1b 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -83,7 +83,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug( OpenApiSpex.Plug.CastAndValidate, [render_error: Pleroma.Web.ApiSpec.RenderError] - when action in [:create, :verify_credentials, :update_credentials] + when action in [:create, :verify_credentials, :update_credentials, :relationships] ) action_fallback(Pleroma.Web.MastodonAPI.FallbackController) @@ -229,7 +229,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "GET /api/v1/accounts/relationships" - def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do + def relationships(%{assigns: %{user: user}} = conn, %{id: id}) do targets = User.get_all_by_ids(List.wrap(id)) render(conn, "relationships.json", user: user, targets: targets) -- cgit v1.2.3 From 278b3fa0ad0ca58a9e5549e98d24944bbe0bf766 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 7 Apr 2020 18:53:12 +0400 Subject: Add spec for AccountController.show --- lib/pleroma/web/api_spec/operations/account_operation.ex | 16 +++++++++++++++- .../web/mastodon_api/controllers/account_controller.ex | 4 ++-- 2 files changed, 17 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 352f66e9d..5b1b2eb4c 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -86,7 +86,21 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do end def show_operation do - :ok + %Operation{ + tags: ["accounts"], + summary: "Account", + operationId: "AccountController.show", + description: "View information about a profile.", + parameters: [ + Operation.parameter(:id, :path, :string, "Account ID or nickname", + example: "123", + required: true + ) + ], + responses: %{ + 200 => Operation.response("Account", "application/json", Account) + } + } end def statuses_operation do diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 1652e3a1b..67375f31c 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -83,7 +83,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug( OpenApiSpex.Plug.CastAndValidate, [render_error: Pleroma.Web.ApiSpec.RenderError] - when action in [:create, :verify_credentials, :update_credentials, :relationships] + when action in [:create, :verify_credentials, :update_credentials, :relationships, :show] ) action_fallback(Pleroma.Web.MastodonAPI.FallbackController) @@ -239,7 +239,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, []) @doc "GET /api/v1/accounts/:id" - def show(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do + def show(%{assigns: %{user: for_user}} = conn, %{id: nickname_or_id}) do with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user), true <- User.visible_for?(user, for_user) do render(conn, "show.json", user: user, for: for_user) -- cgit v1.2.3 From 03124c96cc192ef8c4893738a0cee552c6984da6 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 8 Apr 2020 22:33:25 +0400 Subject: Add spec for AccountController.statuses --- lib/pleroma/web/activity_pub/activity_pub.ex | 11 +- lib/pleroma/web/api_spec.ex | 8 + .../web/api_spec/operations/account_operation.ex | 45 +++- .../schemas/account_update_credentials_request.ex | 5 +- lib/pleroma/web/api_spec/schemas/boolean_like.ex | 36 ++++ lib/pleroma/web/api_spec/schemas/poll.ex | 35 ++++ lib/pleroma/web/api_spec/schemas/status.ex | 227 +++++++++++++++++++++ .../web/api_spec/schemas/statuses_response.ex | 13 ++ .../mastodon_api/controllers/account_controller.ex | 17 +- lib/pleroma/web/mastodon_api/views/status_view.ex | 8 +- 10 files changed, 383 insertions(+), 22 deletions(-) create mode 100644 lib/pleroma/web/api_spec/schemas/boolean_like.ex create mode 100644 lib/pleroma/web/api_spec/schemas/poll.ex create mode 100644 lib/pleroma/web/api_spec/schemas/status.ex create mode 100644 lib/pleroma/web/api_spec/schemas/statuses_response.ex (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 86b105b7f..1909ce097 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -853,7 +853,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end defp exclude_visibility(query, %{"exclude_visibilities" => visibility}) - when visibility not in @valid_visibilities do + when visibility not in [nil | @valid_visibilities] do Logger.error("Could not exclude visibility to #{visibility}") query end @@ -1060,7 +1060,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do raise "Can't use the child object without preloading!" end - defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do + defp restrict_media(query, %{"only_media" => val}) when val in [true, "true", "1"] do from( [_activity, object] in query, where: fragment("not (?)->'attachment' = (?)", object.data, ^[]) @@ -1069,7 +1069,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_media(query, _), do: query - defp restrict_replies(query, %{"exclude_replies" => val}) when val == "true" or val == "1" do + defp restrict_replies(query, %{"exclude_replies" => val}) when val in [true, "true", "1"] do from( [_activity, object] in query, where: fragment("?->>'inReplyTo' is null", object.data) @@ -1078,7 +1078,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_replies(query, _), do: query - defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val == "true" or val == "1" do + defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val in [true, "true", "1"] do from(activity in query, where: fragment("?->>'type' != 'Announce'", activity.data)) end @@ -1157,7 +1157,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do ) end - defp restrict_pinned(query, %{"pinned" => "true", "pinned_activity_ids" => ids}) do + defp restrict_pinned(query, %{"pinned" => pinned, "pinned_activity_ids" => ids}) + when pinned in [true, "true", "1"] do from(activity in query, where: activity.id in ^ids) end diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex index c85fe30d1..d11e776d0 100644 --- a/lib/pleroma/web/api_spec.ex +++ b/lib/pleroma/web/api_spec.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.ApiSpec do alias OpenApiSpex.OpenApi + alias OpenApiSpex.Operation alias Pleroma.Web.Endpoint alias Pleroma.Web.Router @@ -24,6 +25,13 @@ defmodule Pleroma.Web.ApiSpec do # populate the paths from a phoenix router paths: OpenApiSpex.Paths.from_router(Router), components: %OpenApiSpex.Components{ + parameters: %{ + "accountIdOrNickname" => + Operation.parameter(:id, :path, :string, "Account ID or nickname", + example: "123", + required: true + ) + }, securitySchemes: %{ "oAuth" => %OpenApiSpex.SecurityScheme{ type: "oauth2", diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 5b1b2eb4c..09e6d24ed 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do alias OpenApiSpex.Operation + alias OpenApiSpex.Reference alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Helpers alias Pleroma.Web.ApiSpec.Schemas.Account @@ -11,6 +12,9 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse alias Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest + alias Pleroma.Web.ApiSpec.Schemas.BooleanLike + alias Pleroma.Web.ApiSpec.Schemas.StatusesResponse + alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope @spec open_api_operation(atom) :: Operation.t() def open_api_operation(action) do @@ -91,12 +95,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do summary: "Account", operationId: "AccountController.show", description: "View information about a profile.", - parameters: [ - Operation.parameter(:id, :path, :string, "Account ID or nickname", - example: "123", - required: true - ) - ], + parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}], responses: %{ 200 => Operation.response("Account", "application/json", Account) } @@ -104,7 +103,39 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do end def statuses_operation do - :ok + %Operation{ + tags: ["accounts"], + summary: "Statuses", + operationId: "AccountController.statuses", + description: + "Statuses posted to the given account. Public (for public statuses only), or user token + `read:statuses` (for private statuses the user is authorized to see)", + parameters: [ + %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}, + Operation.parameter(:pinned, :query, BooleanLike, "Pinned"), + Operation.parameter(:tagged, :query, :string, "With tag"), + Operation.parameter(:only_media, :query, BooleanLike, "Only meadia"), + Operation.parameter(:with_muted, :query, BooleanLike, "With muted"), + Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblobs"), + Operation.parameter( + :exclude_visibilities, + :query, + %Schema{type: :array, items: VisibilityScope}, + "Exclude visibilities" + ), + Operation.parameter(:max_id, :query, :string, "Max ID"), + Operation.parameter(:min_id, :query, :string, "Mix ID"), + Operation.parameter(:since_id, :query, :string, "Since ID"), + Operation.parameter( + :limit, + :query, + %Schema{type: :integer, default: 20, maximum: 40}, + "Limit" + ) + ], + responses: %{ + 200 => Operation.response("Statuses", "application/json", StatusesResponse) + } + } end def followers_operation do diff --git a/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex b/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex index 6ab48193e..35220c78a 100644 --- a/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex +++ b/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex @@ -38,7 +38,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest do description: "Whether manual approval of follow requests is required." }, fields_attributes: %Schema{ - oneOf: [%Schema{type: :array, items: AccountAttributeField}, %Schema{type: :object}] + oneOf: [ + %Schema{type: :array, items: AccountAttributeField}, + %Schema{type: :object, additionalProperties: %Schema{type: AccountAttributeField}} + ] }, # NOTE: `source` field is not supported # diff --git a/lib/pleroma/web/api_spec/schemas/boolean_like.ex b/lib/pleroma/web/api_spec/schemas/boolean_like.ex new file mode 100644 index 000000000..f3bfb74da --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/boolean_like.ex @@ -0,0 +1,36 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.BooleanLike do + alias OpenApiSpex.Schema + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "BooleanLike", + description: """ + The following values will be treated as `false`: + - false + - 0 + - "0", + - "f", + - "F", + - "false", + - "FALSE", + - "off", + - "OFF" + + All other non-null values will be treated as `true` + """, + anyOf: [ + %Schema{type: :boolean}, + %Schema{type: :string}, + %Schema{type: :integer} + ] + }) + + def after_cast(value, _schmea) do + {:ok, Pleroma.Web.ControllerHelper.truthy_param?(value)} + end +end diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex new file mode 100644 index 000000000..2a9975f85 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/poll.ex @@ -0,0 +1,35 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.Poll do + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "Poll", + description: "Response schema for account custom fields", + type: :object, + properties: %{ + id: %Schema{type: :string}, + expires_at: %Schema{type: :string, format: "date-time"}, + expired: %Schema{type: :boolean}, + multiple: %Schema{type: :boolean}, + votes_count: %Schema{type: :integer}, + voted: %Schema{type: :boolean}, + emojis: %Schema{type: :array, items: AccountEmoji}, + options: %Schema{ + type: :array, + items: %Schema{ + type: :object, + properties: %{ + title: %Schema{type: :string}, + votes_count: %Schema{type: :integer} + } + } + } + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex new file mode 100644 index 000000000..486c3a0fe --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -0,0 +1,227 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.Status do + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.Account + alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji + alias Pleroma.Web.ApiSpec.Schemas.Poll + alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "Status", + description: "Response schema for a status", + type: :object, + properties: %{ + account: Account, + application: %Schema{ + type: :object, + properties: %{ + name: %Schema{type: :string}, + website: %Schema{type: :string, nullable: true} + } + }, + bookmarked: %Schema{type: :boolean}, + card: %Schema{ + type: :object, + nullable: true, + properties: %{ + type: %Schema{type: :string}, + provider_name: %Schema{type: :string}, + provider_url: %Schema{type: :string}, + url: %Schema{type: :string}, + image: %Schema{type: :string}, + title: %Schema{type: :string}, + description: %Schema{type: :string} + } + }, + content: %Schema{type: :string}, + created_at: %Schema{type: :string, format: "date-time"}, + emojis: %Schema{type: :array, items: AccountEmoji}, + favourited: %Schema{type: :boolean}, + favourites_count: %Schema{type: :integer}, + id: %Schema{type: :string}, + in_reply_to_account_id: %Schema{type: :string, nullable: true}, + in_reply_to_id: %Schema{type: :string, nullable: true}, + language: %Schema{type: :string, nullable: true}, + media_attachments: %Schema{ + type: :array, + items: %Schema{ + type: :object, + properties: %{ + id: %Schema{type: :string}, + url: %Schema{type: :string}, + remote_url: %Schema{type: :string}, + preview_url: %Schema{type: :string}, + text_url: %Schema{type: :string}, + description: %Schema{type: :string}, + type: %Schema{type: :string, enum: ["image", "video", "audio", "unknown"]}, + pleroma: %Schema{ + type: :object, + properties: %{mime_type: %Schema{type: :string}} + } + } + } + }, + mentions: %Schema{ + type: :array, + items: %Schema{ + type: :object, + properties: %{ + id: %Schema{type: :string}, + acct: %Schema{type: :string}, + username: %Schema{type: :string}, + url: %Schema{type: :string} + } + } + }, + muted: %Schema{type: :boolean}, + pinned: %Schema{type: :boolean}, + pleroma: %Schema{ + type: :object, + properties: %{ + content: %Schema{type: :object, additionalProperties: %Schema{type: :string}}, + conversation_id: %Schema{type: :integer}, + direct_conversation_id: %Schema{type: :string, nullable: true}, + emoji_reactions: %Schema{ + type: :array, + items: %Schema{ + type: :object, + properties: %{ + name: %Schema{type: :string}, + count: %Schema{type: :integer}, + me: %Schema{type: :boolean} + } + } + }, + expires_at: %Schema{type: :string, format: "date-time", nullable: true}, + in_reply_to_account_acct: %Schema{type: :string, nullable: true}, + local: %Schema{type: :boolean}, + spoiler_text: %Schema{type: :object, additionalProperties: %Schema{type: :string}}, + thread_muted: %Schema{type: :boolean} + } + }, + poll: %Schema{type: Poll, nullable: true}, + reblog: %Schema{ + allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}], + nullable: true + }, + reblogged: %Schema{type: :boolean}, + reblogs_count: %Schema{type: :integer}, + replies_count: %Schema{type: :integer}, + sensitive: %Schema{type: :boolean}, + spoiler_text: %Schema{type: :string}, + tags: %Schema{ + type: :array, + items: %Schema{ + type: :object, + properties: %{ + name: %Schema{type: :string}, + url: %Schema{type: :string} + } + } + }, + uri: %Schema{type: :string}, + url: %Schema{type: :string}, + visibility: VisibilityScope + }, + example: %{ + "JSON" => %{ + "account" => %{ + "acct" => "nick6", + "avatar" => "http://localhost:4001/images/avi.png", + "avatar_static" => "http://localhost:4001/images/avi.png", + "bot" => false, + "created_at" => "2020-04-07T19:48:51.000Z", + "display_name" => "Test テスト User 6", + "emojis" => [], + "fields" => [], + "followers_count" => 1, + "following_count" => 0, + "header" => "http://localhost:4001/images/banner.png", + "header_static" => "http://localhost:4001/images/banner.png", + "id" => "9toJCsKN7SmSf3aj5c", + "locked" => false, + "note" => "Tester Number 6", + "pleroma" => %{ + "background_image" => nil, + "confirmation_pending" => false, + "hide_favorites" => true, + "hide_followers" => false, + "hide_followers_count" => false, + "hide_follows" => false, + "hide_follows_count" => false, + "is_admin" => false, + "is_moderator" => false, + "relationship" => %{ + "blocked_by" => false, + "blocking" => false, + "domain_blocking" => false, + "endorsed" => false, + "followed_by" => false, + "following" => true, + "id" => "9toJCsKN7SmSf3aj5c", + "muting" => false, + "muting_notifications" => false, + "requested" => false, + "showing_reblogs" => true, + "subscribing" => false + }, + "skip_thread_containment" => false, + "tags" => [] + }, + "source" => %{ + "fields" => [], + "note" => "Tester Number 6", + "pleroma" => %{"actor_type" => "Person", "discoverable" => false}, + "sensitive" => false + }, + "statuses_count" => 1, + "url" => "http://localhost:4001/users/nick6", + "username" => "nick6" + }, + "application" => %{"name" => "Web", "website" => nil}, + "bookmarked" => false, + "card" => nil, + "content" => "foobar", + "created_at" => "2020-04-07T19:48:51.000Z", + "emojis" => [], + "favourited" => false, + "favourites_count" => 0, + "id" => "9toJCu5YZW7O7gfvH6", + "in_reply_to_account_id" => nil, + "in_reply_to_id" => nil, + "language" => nil, + "media_attachments" => [], + "mentions" => [], + "muted" => false, + "pinned" => false, + "pleroma" => %{ + "content" => %{"text/plain" => "foobar"}, + "conversation_id" => 345_972, + "direct_conversation_id" => nil, + "emoji_reactions" => [], + "expires_at" => nil, + "in_reply_to_account_acct" => nil, + "local" => true, + "spoiler_text" => %{"text/plain" => ""}, + "thread_muted" => false + }, + "poll" => nil, + "reblog" => nil, + "reblogged" => false, + "reblogs_count" => 0, + "replies_count" => 0, + "sensitive" => false, + "spoiler_text" => "", + "tags" => [], + "uri" => "http://localhost:4001/objects/0f5dad44-0e9e-4610-b377-a2631e499190", + "url" => "http://localhost:4001/notice/9toJCu5YZW7O7gfvH6", + "visibility" => "private" + } + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/statuses_response.ex b/lib/pleroma/web/api_spec/schemas/statuses_response.ex new file mode 100644 index 000000000..fb7c7e0aa --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/statuses_response.ex @@ -0,0 +1,13 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.StatusesResponse do + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "StatusesResponse", + type: :array, + items: Pleroma.Web.ApiSpec.Schemas.Status + }) +end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 67375f31c..208df5698 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -83,7 +83,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug( OpenApiSpex.Plug.CastAndValidate, [render_error: Pleroma.Web.ApiSpec.RenderError] - when action in [:create, :verify_credentials, :update_credentials, :relationships, :show] + when action in [ + :create, + :verify_credentials, + :update_credentials, + :relationships, + :show, + :statuses + ] ) action_fallback(Pleroma.Web.MastodonAPI.FallbackController) @@ -250,12 +257,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "GET /api/v1/accounts/:id/statuses" def statuses(%{assigns: %{user: reading_user}} = conn, params) do - with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user), + with %User{} = user <- User.get_cached_by_nickname_or_id(params.id, for: reading_user), true <- User.visible_for?(user, reading_user) do params = params - |> Map.put("tag", params["tagged"]) - |> Map.delete("godmode") + |> Map.delete(:tagged) + |> Enum.filter(&(not is_nil(&1))) + |> Map.new(fn {key, value} -> {to_string(key), value} end) + |> Map.put("tag", params[:tagged]) activities = ActivityPub.fetch_user_activities(user, reading_user, params) diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index b5850e1ae..ba40fd63e 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -521,11 +521,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do """ @spec build_tags(list(any())) :: list(map()) def build_tags(object_tags) when is_list(object_tags) do - object_tags = for tag when is_binary(tag) <- object_tags, do: tag - - Enum.reduce(object_tags, [], fn tag, tags -> - tags ++ [%{name: tag, url: "/tag/#{URI.encode(tag)}"}] - end) + object_tags + |> Enum.filter(&is_binary/1) + |> Enum.map(&%{name: &1, url: "/tag/#{URI.encode(&1)}"}) end def build_tags(_), do: [] -- cgit v1.2.3 From bd6e2b300f82e66afb121c2339c3cbbfb0b1a446 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 8 Apr 2020 23:16:20 +0400 Subject: Add spec for AccountController.followers --- .../web/api_spec/operations/account_operation.ex | 19 ++++++++++++++++++- lib/pleroma/web/api_spec/schemas/accounts_response.ex | 13 +++++++++++++ .../mastodon_api/controllers/account_controller.ex | 8 +++++++- 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 lib/pleroma/web/api_spec/schemas/accounts_response.ex (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 09e6d24ed..070c74758 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -11,6 +11,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse alias Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse + alias Pleroma.Web.ApiSpec.Schemas.AccountsResponse alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest alias Pleroma.Web.ApiSpec.Schemas.BooleanLike alias Pleroma.Web.ApiSpec.Schemas.StatusesResponse @@ -139,7 +140,23 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do end def followers_operation do - :ok + %Operation{ + tags: ["accounts"], + summary: "Followers", + operationId: "AccountController.followers", + security: [%{"oAuth" => ["read:accounts"]}], + description: + "Accounts which follow the given account, if network is not hidden by the account owner.", + parameters: [ + %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}, + Operation.parameter(:max_id, :query, :string, "Max ID"), + Operation.parameter(:since_id, :query, :string, "Since ID"), + Operation.parameter(:limit, :query, :integer, "Limit") + ], + responses: %{ + 200 => Operation.response("Accounts", "application/json", AccountsResponse) + } + } end def following_operation, do: :ok diff --git a/lib/pleroma/web/api_spec/schemas/accounts_response.ex b/lib/pleroma/web/api_spec/schemas/accounts_response.ex new file mode 100644 index 000000000..b714f59e7 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/accounts_response.ex @@ -0,0 +1,13 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.AccountsResponse do + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "AccountsResponse", + type: :array, + items: Pleroma.Web.ApiSpec.Schemas.Account + }) +end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 208df5698..1ffccdd1d 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -89,7 +89,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do :update_credentials, :relationships, :show, - :statuses + :statuses, + :followers ] ) @@ -284,6 +285,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "GET /api/v1/accounts/:id/followers" def followers(%{assigns: %{user: for_user, account: user}} = conn, params) do + params = + params + |> Enum.map(fn {key, value} -> {to_string(key), value} end) + |> Enum.into(%{}) + followers = cond do for_user && user.id == for_user.id -> MastodonAPI.get_followers(user, params) -- cgit v1.2.3 From e105cc12b67e44eb4e19293b850731f300999a4f Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 8 Apr 2020 23:38:07 +0400 Subject: Add spec for AccountController.following --- .../web/api_spec/operations/account_operation.ex | 35 ++++++++++++++++++++-- .../mastodon_api/controllers/account_controller.ex | 8 ++++- 2 files changed, 40 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 070c74758..456d08a45 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -150,8 +150,40 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do parameters: [ %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}, Operation.parameter(:max_id, :query, :string, "Max ID"), + Operation.parameter(:min_id, :query, :string, "Mix ID"), Operation.parameter(:since_id, :query, :string, "Since ID"), - Operation.parameter(:limit, :query, :integer, "Limit") + Operation.parameter( + :limit, + :query, + %Schema{type: :integer, default: 20, maximum: 40}, + "Limit" + ) + ], + responses: %{ + 200 => Operation.response("Accounts", "application/json", AccountsResponse) + } + } + end + + def following_operation do + %Operation{ + tags: ["accounts"], + summary: "Following", + operationId: "AccountController.following", + security: [%{"oAuth" => ["read:accounts"]}], + description: + "Accounts which the given account is following, if network is not hidden by the account owner.", + parameters: [ + %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}, + Operation.parameter(:max_id, :query, :string, "Max ID"), + Operation.parameter(:min_id, :query, :string, "Mix ID"), + Operation.parameter(:since_id, :query, :string, "Since ID"), + Operation.parameter( + :limit, + :query, + %Schema{type: :integer, default: 20, maximum: 40}, + "Limit" + ) ], responses: %{ 200 => Operation.response("Accounts", "application/json", AccountsResponse) @@ -159,7 +191,6 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do } end - def following_operation, do: :ok def lists_operation, do: :ok def follow_operation, do: :ok def unfollow_operation, do: :ok diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 1ffccdd1d..e74180662 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -90,7 +90,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do :relationships, :show, :statuses, - :followers + :followers, + :following ] ) @@ -304,6 +305,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "GET /api/v1/accounts/:id/following" def following(%{assigns: %{user: for_user, account: user}} = conn, params) do + params = + params + |> Enum.map(fn {key, value} -> {to_string(key), value} end) + |> Enum.into(%{}) + followers = cond do for_user && user.id == for_user.id -> MastodonAPI.get_friends(user, params) -- cgit v1.2.3 From 1b680a98ae15035215fa8489f825af72532340c4 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 8 Apr 2020 23:51:46 +0400 Subject: Add spec for AccountController.lists --- .../web/api_spec/operations/account_operation.ex | 18 +++++++++++++++- lib/pleroma/web/api_spec/schemas/list.ex | 25 ++++++++++++++++++++++ lib/pleroma/web/api_spec/schemas/lists_response.ex | 16 ++++++++++++++ .../mastodon_api/controllers/account_controller.ex | 3 ++- 4 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 lib/pleroma/web/api_spec/schemas/list.ex create mode 100644 lib/pleroma/web/api_spec/schemas/lists_response.ex (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 456d08a45..ad10f4ec9 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -14,6 +14,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do alias Pleroma.Web.ApiSpec.Schemas.AccountsResponse alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest alias Pleroma.Web.ApiSpec.Schemas.BooleanLike + alias Pleroma.Web.ApiSpec.Schemas.ListsResponse alias Pleroma.Web.ApiSpec.Schemas.StatusesResponse alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope @@ -191,7 +192,22 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do } end - def lists_operation, do: :ok + def lists_operation do + %Operation{ + tags: ["accounts"], + summary: "Lists containing this account", + operationId: "AccountController.lists", + security: [%{"oAuth" => ["read:lists"]}], + description: "User lists that you have added this account to.", + parameters: [ + %Reference{"$ref": "#/components/parameters/accountIdOrNickname"} + ], + responses: %{ + 200 => Operation.response("Lists", "application/json", ListsResponse) + } + } + end + def follow_operation, do: :ok def unfollow_operation, do: :ok def mute_operation, do: :ok diff --git a/lib/pleroma/web/api_spec/schemas/list.ex b/lib/pleroma/web/api_spec/schemas/list.ex new file mode 100644 index 000000000..30fa7db93 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/list.ex @@ -0,0 +1,25 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.List do + alias OpenApiSpex.Schema + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "List", + description: "Response schema for a list", + type: :object, + properties: %{ + id: %Schema{type: :string}, + title: %Schema{type: :string} + }, + example: %{ + "JSON" => %{ + "id" => "123", + "title" => "my list" + } + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/lists_response.ex b/lib/pleroma/web/api_spec/schemas/lists_response.ex new file mode 100644 index 000000000..132454579 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/lists_response.ex @@ -0,0 +1,16 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.ListsResponse do + alias Pleroma.Web.ApiSpec.Schemas.List + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "ListsResponse", + description: "Response schema for lists", + type: :array, + items: List + }) +end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index e74180662..2c5cd8cde 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -91,7 +91,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do :show, :statuses, :followers, - :following + :following, + :lists ] ) -- cgit v1.2.3 From 854780c72bc90a55d7005a05861d45771c5aeadf Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 9 Apr 2020 15:25:24 +0400 Subject: Add spec for AccountController.follow --- lib/pleroma/web/api_spec.ex | 2 +- .../web/api_spec/operations/account_operation.ex | 28 +++++++++++--- .../web/api_spec/schemas/account_relationship.ex | 45 ++++++++++++++++++++++ .../schemas/account_relationship_response.ex | 43 --------------------- .../schemas/account_relationships_response.ex | 5 ++- .../mastodon_api/controllers/account_controller.ex | 7 ++-- 6 files changed, 76 insertions(+), 54 deletions(-) create mode 100644 lib/pleroma/web/api_spec/schemas/account_relationship.ex delete mode 100644 lib/pleroma/web/api_spec/schemas/account_relationship_response.ex (limited to 'lib') diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex index d11e776d0..b3c1e3ea2 100644 --- a/lib/pleroma/web/api_spec.ex +++ b/lib/pleroma/web/api_spec.ex @@ -39,7 +39,7 @@ defmodule Pleroma.Web.ApiSpec do password: %OpenApiSpex.OAuthFlow{ authorizationUrl: "/oauth/authorize", tokenUrl: "/oauth/token", - scopes: %{"read" => "read", "write" => "write"} + scopes: %{"read" => "read", "write" => "write", "follow" => "follow"} } } } diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index ad10f4ec9..a76141f7a 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do alias Pleroma.Web.ApiSpec.Schemas.Account alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse + alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship alias Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse alias Pleroma.Web.ApiSpec.Schemas.AccountsResponse alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest @@ -186,9 +187,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do "Limit" ) ], - responses: %{ - 200 => Operation.response("Accounts", "application/json", AccountsResponse) - } + responses: %{200 => Operation.response("Accounts", "application/json", AccountsResponse)} } end @@ -199,16 +198,33 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do operationId: "AccountController.lists", security: [%{"oAuth" => ["read:lists"]}], description: "User lists that you have added this account to.", + parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}], + responses: %{200 => Operation.response("Lists", "application/json", ListsResponse)} + } + end + + def follow_operation do + %Operation{ + tags: ["accounts"], + summary: "Follow", + operationId: "AccountController.follow", + security: [%{"oAuth" => ["follow", "write:follows"]}], + description: "Follow the given account", parameters: [ - %Reference{"$ref": "#/components/parameters/accountIdOrNickname"} + %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}, + Operation.parameter( + :reblogs, + :query, + BooleanLike, + "Receive this account's reblogs in home timeline? Defaults to true." + ) ], responses: %{ - 200 => Operation.response("Lists", "application/json", ListsResponse) + 200 => Operation.response("Relationship", "application/json", AccountRelationship) } } end - def follow_operation, do: :ok def unfollow_operation, do: :ok def mute_operation, do: :ok def unmute_operation, do: :ok diff --git a/lib/pleroma/web/api_spec/schemas/account_relationship.ex b/lib/pleroma/web/api_spec/schemas/account_relationship.ex new file mode 100644 index 000000000..7db3b49bb --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/account_relationship.ex @@ -0,0 +1,45 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do + alias OpenApiSpex.Schema + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "AccountRelationship", + description: "Response schema for relationship", + type: :object, + properties: %{ + blocked_by: %Schema{type: :boolean}, + blocking: %Schema{type: :boolean}, + domain_blocking: %Schema{type: :boolean}, + endorsed: %Schema{type: :boolean}, + followed_by: %Schema{type: :boolean}, + following: %Schema{type: :boolean}, + id: %Schema{type: :string}, + muting: %Schema{type: :boolean}, + muting_notifications: %Schema{type: :boolean}, + requested: %Schema{type: :boolean}, + showing_reblogs: %Schema{type: :boolean}, + subscribing: %Schema{type: :boolean} + }, + example: %{ + "JSON" => %{ + "blocked_by" => false, + "blocking" => false, + "domain_blocking" => false, + "endorsed" => false, + "followed_by" => false, + "following" => false, + "id" => "9tKi3esbG7OQgZ2920", + "muting" => false, + "muting_notifications" => false, + "requested" => false, + "showing_reblogs" => true, + "subscribing" => false + } + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/account_relationship_response.ex b/lib/pleroma/web/api_spec/schemas/account_relationship_response.ex deleted file mode 100644 index 9974b946b..000000000 --- a/lib/pleroma/web/api_spec/schemas/account_relationship_response.ex +++ /dev/null @@ -1,43 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationshipResponse do - alias OpenApiSpex.Schema - - require OpenApiSpex - - OpenApiSpex.schema(%{ - title: "AccountRelationshipResponse", - description: "Response schema for an account relationship", - type: :object, - properties: %{ - id: %Schema{type: :string}, - following: %Schema{type: :boolean}, - showing_reblogs: %Schema{type: :boolean}, - followed_by: %Schema{type: :boolean}, - blocking: %Schema{type: :boolean}, - blocked_by: %Schema{type: :boolean}, - muting: %Schema{type: :boolean}, - muting_notifications: %Schema{type: :boolean}, - requested: %Schema{type: :boolean}, - domain_blocking: %Schema{type: :boolean}, - endorsed: %Schema{type: :boolean} - }, - example: %{ - "JSON" => %{ - "id" => "1", - "following" => true, - "showing_reblogs" => true, - "followed_by" => true, - "blocking" => false, - "blocked_by" => false, - "muting" => false, - "muting_notifications" => false, - "requested" => false, - "domain_blocking" => false, - "endorsed" => false - } - } - }) -end diff --git a/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex b/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex index 2ca632310..960e14db1 100644 --- a/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex +++ b/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse do title: "AccountRelationshipsResponse", description: "Response schema for account relationships", type: :array, - items: Pleroma.Web.ApiSpec.Schemas.AccountRelationshipResponse, + items: Pleroma.Web.ApiSpec.Schemas.AccountRelationship, example: [ %{ "id" => "1", @@ -22,6 +22,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse do "muting_notifications" => false, "requested" => false, "domain_blocking" => false, + "subscribing" => false, "endorsed" => true }, %{ @@ -35,6 +36,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse do "muting_notifications" => false, "requested" => true, "domain_blocking" => false, + "subscribing" => false, "endorsed" => false }, %{ @@ -48,6 +50,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse do "muting_notifications" => false, "requested" => false, "domain_blocking" => true, + "subscribing" => true, "endorsed" => false } ] diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 2c5cd8cde..d2ad65ef3 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -92,7 +92,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do :statuses, :followers, :following, - :lists + :lists, + :follow ] ) @@ -337,8 +338,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do {:error, :not_found} end - def follow(%{assigns: %{user: follower, account: followed}} = conn, _params) do - with {:ok, follower} <- MastodonAPI.follow(follower, followed, conn.params) do + def follow(%{assigns: %{user: follower, account: followed}} = conn, params) do + with {:ok, follower} <- MastodonAPI.follow(follower, followed, params) do render(conn, "relationship.json", user: follower, target: followed) else {:error, message} -> json_response(conn, :forbidden, %{error: message}) -- cgit v1.2.3 From aa958a6dda7cdcf12e9cd9232e7c6be421610317 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 9 Apr 2020 17:57:21 +0400 Subject: Add spec for AccountController.unfollow --- lib/pleroma/web/api_spec/operations/account_operation.ex | 15 ++++++++++++++- .../web/mastodon_api/controllers/account_controller.ex | 3 ++- 2 files changed, 16 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index a76141f7a..8925ebefd 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -225,7 +225,20 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do } end - def unfollow_operation, do: :ok + def unfollow_operation do + %Operation{ + tags: ["accounts"], + summary: "Unfollow", + operationId: "AccountController.unfollow", + security: [%{"oAuth" => ["follow", "write:follows"]}], + description: "Unfollow the given account", + parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}], + responses: %{ + 200 => Operation.response("Relationship", "application/json", AccountRelationship) + } + } + end + def mute_operation, do: :ok def unmute_operation, do: :ok def block_operation, do: :ok diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index d2ad65ef3..1ecce2928 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -93,7 +93,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do :followers, :following, :lists, - :follow + :follow, + :unfollow ] ) -- cgit v1.2.3 From e4195d4a684908d58482f9c865375a080e7b78bc Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 9 Apr 2020 18:28:14 +0400 Subject: Add specs for AccountController.mute and AccountController.unmute --- .../web/api_spec/operations/account_operation.ex | 41 ++++++++++++++++++++-- .../web/api_spec/schemas/account_mute_request.ex | 24 +++++++++++++ .../mastodon_api/controllers/account_controller.ex | 10 +++--- 3 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 lib/pleroma/web/api_spec/schemas/account_mute_request.ex (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 8925ebefd..62ae2eead 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do alias Pleroma.Web.ApiSpec.Schemas.Account alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse + alias Pleroma.Web.ApiSpec.Schemas.AccountMuteRequest alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship alias Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse alias Pleroma.Web.ApiSpec.Schemas.AccountsResponse @@ -239,8 +240,44 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do } end - def mute_operation, do: :ok - def unmute_operation, do: :ok + def mute_operation do + %Operation{ + tags: ["accounts"], + summary: "Mute", + operationId: "AccountController.mute", + security: [%{"oAuth" => ["follow", "write:mutes"]}], + requestBody: Helpers.request_body("Parameters", AccountMuteRequest), + description: + "Mute the given account. Clients should filter statuses and notifications from this account, if received (e.g. due to a boost in the Home timeline).", + parameters: [ + %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}, + Operation.parameter( + :notifications, + :query, + %Schema{allOf: [BooleanLike], default: true}, + "Mute notifications in addition to statuses? Defaults to `true`." + ) + ], + responses: %{ + 200 => Operation.response("Relationship", "application/json", AccountRelationship) + } + } + end + + def unmute_operation do + %Operation{ + tags: ["accounts"], + summary: "Unmute", + operationId: "AccountController.unmute", + security: [%{"oAuth" => ["follow", "write:mutes"]}], + description: "Unmute the given account.", + parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}], + responses: %{ + 200 => Operation.response("Relationship", "application/json", AccountRelationship) + } + } + end + def block_operation, do: :ok def unblock_operation, do: :ok def follows_operation, do: :ok diff --git a/lib/pleroma/web/api_spec/schemas/account_mute_request.ex b/lib/pleroma/web/api_spec/schemas/account_mute_request.ex new file mode 100644 index 000000000..a61f6d04c --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/account_mute_request.ex @@ -0,0 +1,24 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.AccountMuteRequest do + alias OpenApiSpex.Schema + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "AccountMuteRequest", + description: "POST body for muting an account", + type: :object, + properties: %{ + notifications: %Schema{ + type: :boolean, + description: "Mute notifications in addition to statuses? Defaults to true.", + default: true + } + }, + example: %{ + "notifications" => true + } + }) +end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 1ecce2928..9aba2e094 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -94,7 +94,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do :following, :lists, :follow, - :unfollow + :unfollow, + :mute, + :unmute ] ) @@ -359,10 +361,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "POST /api/v1/accounts/:id/mute" - def mute(%{assigns: %{user: muter, account: muted}} = conn, params) do - notifications? = params |> Map.get("notifications", true) |> truthy_param?() - - with {:ok, _user_relationships} <- User.mute(muter, muted, notifications?) do + def mute(%{assigns: %{user: muter, account: muted}, body_params: params} = conn, _params) do + with {:ok, _user_relationships} <- User.mute(muter, muted, params.notifications) do render(conn, "relationship.json", user: muter, target: muted) else {:error, message} -> json_response(conn, :forbidden, %{error: message}) -- cgit v1.2.3 From 68a979b8243b9a5b685df2c13388a93b9ede1900 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 9 Apr 2020 18:41:18 +0400 Subject: Add specs for AccountController.block and AccountController.unblock --- .../web/api_spec/operations/account_operation.ex | 31 ++++++++++++++++++++-- .../mastodon_api/controllers/account_controller.ex | 4 ++- 2 files changed, 32 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 62ae2eead..73fbe8785 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -278,8 +278,35 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do } end - def block_operation, do: :ok - def unblock_operation, do: :ok + def block_operation do + %Operation{ + tags: ["accounts"], + summary: "Block", + operationId: "AccountController.block", + security: [%{"oAuth" => ["follow", "write:blocks"]}], + description: + "Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline)", + parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}], + responses: %{ + 200 => Operation.response("Relationship", "application/json", AccountRelationship) + } + } + end + + def unblock_operation do + %Operation{ + tags: ["accounts"], + summary: "Unblock", + operationId: "AccountController.unblock", + security: [%{"oAuth" => ["follow", "write:blocks"]}], + description: "Unblock the given account.", + parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}], + responses: %{ + 200 => Operation.response("Relationship", "application/json", AccountRelationship) + } + } + end + def follows_operation, do: :ok def mutes_operation, do: :ok def blocks_operation, do: :ok diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 9aba2e094..c1f70f32c 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -96,7 +96,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do :follow, :unfollow, :mute, - :unmute + :unmute, + :block, + :unblock ] ) -- cgit v1.2.3 From ab185d3ea47deb38128dc501acdf27c47c542de2 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 9 Apr 2020 20:12:09 +0400 Subject: Add spec for AccountController.follows --- .../web/api_spec/operations/account_operation.ex | 15 ++++++++++++++- .../web/api_spec/schemas/account_follows_request.ex | 18 ++++++++++++++++++ .../web/mastodon_api/controllers/account_controller.ex | 5 +++-- 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 lib/pleroma/web/api_spec/schemas/account_follows_request.ex (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 73fbe8785..9fef7ece1 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do alias Pleroma.Web.ApiSpec.Schemas.Account alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse + alias Pleroma.Web.ApiSpec.Schemas.AccountFollowsRequest alias Pleroma.Web.ApiSpec.Schemas.AccountMuteRequest alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship alias Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse @@ -307,7 +308,19 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do } end - def follows_operation, do: :ok + def follows_operation do + %Operation{ + tags: ["accounts"], + summary: "Follows", + operationId: "AccountController.follows", + security: [%{"oAuth" => ["follow", "write:follows"]}], + requestBody: Helpers.request_body("Parameters", AccountFollowsRequest, required: true), + responses: %{ + 200 => Operation.response("Account", "application/json", Account) + } + } + end + def mutes_operation, do: :ok def blocks_operation, do: :ok def endorsements_operation, do: :ok diff --git a/lib/pleroma/web/api_spec/schemas/account_follows_request.ex b/lib/pleroma/web/api_spec/schemas/account_follows_request.ex new file mode 100644 index 000000000..4fbe615d6 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/account_follows_request.ex @@ -0,0 +1,18 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.AccountFollowsRequest do + alias OpenApiSpex.Schema + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "AccountFollowsRequest", + description: "POST body for muting an account", + type: :object, + properties: %{ + uri: %Schema{type: :string} + }, + required: [:uri] + }) +end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index c1f70f32c..4340b9c84 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -98,7 +98,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do :mute, :unmute, :block, - :unblock + :unblock, + :follows ] ) @@ -401,7 +402,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "POST /api/v1/follows" - def follows(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do + def follows(%{assigns: %{user: follower}, body_params: %{uri: uri}} = conn, _) do with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)}, {_, true} <- {:followed, follower.id != followed.id}, {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do -- cgit v1.2.3 From 7e0b42d99f3eb9520bc29cc29c06512c55183482 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 9 Apr 2020 20:34:21 +0400 Subject: Add specs for AccountController.mutes, AccountController.blocks, AccountController.mutes, AccountController.endorsements --- .../web/api_spec/operations/account_operation.ex | 41 ++++++++++++++++++++-- .../mastodon_api/controllers/account_controller.ex | 23 +----------- 2 files changed, 39 insertions(+), 25 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 9fef7ece1..9749c3b60 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -321,7 +321,42 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do } end - def mutes_operation, do: :ok - def blocks_operation, do: :ok - def endorsements_operation, do: :ok + def mutes_operation do + %Operation{ + tags: ["accounts"], + summary: "Muted accounts", + operationId: "AccountController.mutes", + description: "Accounts the user has muted.", + security: [%{"oAuth" => ["follow", "read:mutes"]}], + responses: %{ + 200 => Operation.response("Accounts", "application/json", AccountsResponse) + } + } + end + + def blocks_operation do + %Operation{ + tags: ["accounts"], + summary: "Blocked users", + operationId: "AccountController.blocks", + description: "View your blocks. See also accounts/:id/{block,unblock}", + security: [%{"oAuth" => ["read:blocks"]}], + responses: %{ + 200 => Operation.response("Accounts", "application/json", AccountsResponse) + } + } + end + + def endorsements_operation do + %Operation{ + tags: ["accounts"], + summary: "Endorsements", + operationId: "AccountController.endorsements", + description: "Not implemented", + security: [%{"oAuth" => ["read:accounts"]}], + responses: %{ + 200 => Operation.response("Empry array", "application/json", %Schema{type: :array}) + } + } + end end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 4340b9c84..f72c91c51 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -80,28 +80,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug(RateLimiter, [name: :app_account_creation] when action == :create) plug(:assign_account_by_id when action in @needs_account) - plug( - OpenApiSpex.Plug.CastAndValidate, - [render_error: Pleroma.Web.ApiSpec.RenderError] - when action in [ - :create, - :verify_credentials, - :update_credentials, - :relationships, - :show, - :statuses, - :followers, - :following, - :lists, - :follow, - :unfollow, - :mute, - :unmute, - :block, - :unblock, - :follows - ] - ) + plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) action_fallback(Pleroma.Web.MastodonAPI.FallbackController) -- cgit v1.2.3 From f7e623c11c4b6f4f323a4317e9489092be73f9cd Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Tue, 14 Apr 2020 20:19:08 +0300 Subject: [#1364] Resolved merge conflicts with `develop`. --- lib/pleroma/notification.ex | 1 - 1 file changed, 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index f517282f7..b76dd176c 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -7,7 +7,6 @@ defmodule Pleroma.Notification do alias Pleroma.Activity alias Pleroma.FollowingRelationship - alias Pleroma.Marker alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Pagination -- cgit v1.2.3 From d3e876aeeebfcdd2821ef8310bd60b785e6df560 Mon Sep 17 00:00:00 2001 From: minibikini Date: Wed, 15 Apr 2020 10:26:44 +0000 Subject: Apply suggestion to lib/pleroma/web/api_spec/operations/account_operation.ex --- lib/pleroma/web/api_spec/operations/account_operation.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 9749c3b60..7ead44197 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -120,7 +120,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do Operation.parameter(:tagged, :query, :string, "With tag"), Operation.parameter(:only_media, :query, BooleanLike, "Only meadia"), Operation.parameter(:with_muted, :query, BooleanLike, "With muted"), - Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblobs"), + Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"), + Operation.parameter( :exclude_visibilities, :query, -- cgit v1.2.3 From a7feca1604fe7f22d10c0fd3284f14eae8609852 Mon Sep 17 00:00:00 2001 From: minibikini Date: Wed, 15 Apr 2020 10:26:53 +0000 Subject: Apply suggestion to lib/pleroma/web/api_spec/operations/account_operation.ex --- lib/pleroma/web/api_spec/operations/account_operation.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 7ead44197..1c726a612 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -119,7 +119,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do Operation.parameter(:pinned, :query, BooleanLike, "Pinned"), Operation.parameter(:tagged, :query, :string, "With tag"), Operation.parameter(:only_media, :query, BooleanLike, "Only meadia"), - Operation.parameter(:with_muted, :query, BooleanLike, "With muted"), + Operation.parameter(:with_muted, :query, BooleanLike, "Include statuses from muted acccounts."), Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"), Operation.parameter( -- cgit v1.2.3 From a794ba655f5a0a8b5512ad718601e5a03b9aebef Mon Sep 17 00:00:00 2001 From: minibikini Date: Wed, 15 Apr 2020 10:27:01 +0000 Subject: Apply suggestion to lib/pleroma/web/api_spec/operations/account_operation.ex --- lib/pleroma/web/api_spec/operations/account_operation.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 1c726a612..6ce2cfe25 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -118,7 +118,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}, Operation.parameter(:pinned, :query, BooleanLike, "Pinned"), Operation.parameter(:tagged, :query, :string, "With tag"), - Operation.parameter(:only_media, :query, BooleanLike, "Only meadia"), + Operation.parameter(:only_media, :query, BooleanLike, "Include only statuses with media attached"), Operation.parameter(:with_muted, :query, BooleanLike, "Include statuses from muted acccounts."), Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"), -- cgit v1.2.3 From bfa26b09370ee049f8d70c4112709f2666c590d1 Mon Sep 17 00:00:00 2001 From: minibikini Date: Wed, 15 Apr 2020 10:30:19 +0000 Subject: Apply suggestion to lib/pleroma/web/api_spec/operations/account_operation.ex --- lib/pleroma/web/api_spec/operations/account_operation.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 6ce2cfe25..7d4f7586d 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -129,7 +129,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do "Exclude visibilities" ), Operation.parameter(:max_id, :query, :string, "Max ID"), - Operation.parameter(:min_id, :query, :string, "Mix ID"), + Operation.parameter(:min_id, :query, :string, "Return the oldest statuses newer than this id. "), Operation.parameter(:since_id, :query, :string, "Since ID"), Operation.parameter( :limit, -- cgit v1.2.3 From a45bd91d4e79ed354ab3903b195cf74e4327d4d0 Mon Sep 17 00:00:00 2001 From: minibikini Date: Wed, 15 Apr 2020 10:48:32 +0000 Subject: Apply suggestion to lib/pleroma/web/api_spec/operations/account_operation.ex --- lib/pleroma/web/api_spec/operations/account_operation.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 7d4f7586d..31dfbb098 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -116,7 +116,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do "Statuses posted to the given account. Public (for public statuses only), or user token + `read:statuses` (for private statuses the user is authorized to see)", parameters: [ %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}, - Operation.parameter(:pinned, :query, BooleanLike, "Pinned"), + Operation.parameter(:pinned, :query, BooleanLike, "Include only pinned statuses"), Operation.parameter(:tagged, :query, :string, "With tag"), Operation.parameter(:only_media, :query, BooleanLike, "Include only statuses with media attached"), Operation.parameter(:with_muted, :query, BooleanLike, "Include statuses from muted acccounts."), -- cgit v1.2.3 From 81a4c15816bf4fbe3e70ba1d34adff5dfaee1cbc Mon Sep 17 00:00:00 2001 From: minibikini Date: Wed, 15 Apr 2020 10:48:52 +0000 Subject: Apply suggestion to lib/pleroma/web/api_spec/operations/account_operation.ex --- lib/pleroma/web/api_spec/operations/account_operation.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 31dfbb098..dee28d1aa 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -128,7 +128,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do %Schema{type: :array, items: VisibilityScope}, "Exclude visibilities" ), - Operation.parameter(:max_id, :query, :string, "Max ID"), + Operation.parameter(:max_id, :query, :string, "Return statuses older than this id"), Operation.parameter(:min_id, :query, :string, "Return the oldest statuses newer than this id. "), Operation.parameter(:since_id, :query, :string, "Since ID"), Operation.parameter( -- cgit v1.2.3 From 5a2e45a2189514662f46a293f764682daba7b52d Mon Sep 17 00:00:00 2001 From: minibikini Date: Wed, 15 Apr 2020 11:29:10 +0000 Subject: Apply suggestion to lib/pleroma/web/api_spec/operations/account_operation.ex --- lib/pleroma/web/api_spec/operations/account_operation.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index dee28d1aa..92622e2ff 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -130,7 +130,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do ), Operation.parameter(:max_id, :query, :string, "Return statuses older than this id"), Operation.parameter(:min_id, :query, :string, "Return the oldest statuses newer than this id. "), - Operation.parameter(:since_id, :query, :string, "Since ID"), + Operation.parameter(:since_id, :query, :string, "Return the newest statuses newer than this id. "), Operation.parameter( :limit, :query, -- cgit v1.2.3 From 8ed162b65538ee3cb5b125587fd65657b36ca143 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 15 Apr 2020 15:39:32 +0400 Subject: Fix formatting --- .../web/api_spec/operations/account_operation.ex | 31 +++++++++++++++++----- 1 file changed, 25 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 92622e2ff..6c9de51bb 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -118,19 +118,38 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}, Operation.parameter(:pinned, :query, BooleanLike, "Include only pinned statuses"), Operation.parameter(:tagged, :query, :string, "With tag"), - Operation.parameter(:only_media, :query, BooleanLike, "Include only statuses with media attached"), - Operation.parameter(:with_muted, :query, BooleanLike, "Include statuses from muted acccounts."), + Operation.parameter( + :only_media, + :query, + BooleanLike, + "Include only statuses with media attached" + ), + Operation.parameter( + :with_muted, + :query, + BooleanLike, + "Include statuses from muted acccounts." + ), Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"), - Operation.parameter( :exclude_visibilities, :query, %Schema{type: :array, items: VisibilityScope}, "Exclude visibilities" ), - Operation.parameter(:max_id, :query, :string, "Return statuses older than this id"), - Operation.parameter(:min_id, :query, :string, "Return the oldest statuses newer than this id. "), - Operation.parameter(:since_id, :query, :string, "Return the newest statuses newer than this id. "), + Operation.parameter(:max_id, :query, :string, "Return statuses older than this ID"), + Operation.parameter( + :min_id, + :query, + :string, + "Return the oldest statuses newer than this ID" + ), + Operation.parameter( + :since_id, + :query, + :string, + "Return the newest statuses newer than this ID" + ), Operation.parameter( :limit, :query, -- cgit v1.2.3 From 0e647ff55aa3128f45cd9df79b8af06da57c009e Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 15 Apr 2020 16:45:45 +0400 Subject: Abstract pagination params in OpenAPI spec --- lib/pleroma/web/api_spec/helpers.ex | 22 +++++ .../web/api_spec/operations/account_operation.ex | 108 +++++++-------------- 2 files changed, 57 insertions(+), 73 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex index 7348dcbee..ce40fb9e8 100644 --- a/lib/pleroma/web/api_spec/helpers.ex +++ b/lib/pleroma/web/api_spec/helpers.ex @@ -3,6 +3,9 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ApiSpec.Helpers do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + def request_body(description, schema_ref, opts \\ []) do media_types = ["application/json", "multipart/form-data", "application/x-www-form-urlencoded"] @@ -24,4 +27,23 @@ defmodule Pleroma.Web.ApiSpec.Helpers do required: opts[:required] || false } end + + def pagination_params do + [ + Operation.parameter(:max_id, :query, :string, "Return items older than this ID"), + Operation.parameter(:min_id, :query, :string, "Return the oldest items newer than this ID"), + Operation.parameter( + :since_id, + :query, + :string, + "Return the newest items newer than this ID" + ), + Operation.parameter( + :limit, + :query, + %Schema{type: :integer, default: 20, maximum: 40}, + "Limit" + ) + ] + end end diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 6c9de51bb..fe44a917a 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -6,7 +6,6 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do alias OpenApiSpex.Operation alias OpenApiSpex.Reference alias OpenApiSpex.Schema - alias Pleroma.Web.ApiSpec.Helpers alias Pleroma.Web.ApiSpec.Schemas.Account alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse @@ -21,6 +20,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do alias Pleroma.Web.ApiSpec.Schemas.StatusesResponse alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope + import Pleroma.Web.ApiSpec.Helpers + @spec open_api_operation(atom) :: Operation.t() def open_api_operation(action) do operation = String.to_existing_atom("#{action}_operation") @@ -35,7 +36,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do description: "Creates a user and account records. Returns an account access token for the app that initiated the request. The app should save this token for later, and should wait for the user to confirm their account by clicking a link in their email inbox.", operationId: "AccountController.create", - requestBody: Helpers.request_body("Parameters", AccountCreateRequest, required: true), + requestBody: request_body("Parameters", AccountCreateRequest, required: true), responses: %{ 200 => Operation.response("Account", "application/json", AccountCreateResponse) } @@ -62,8 +63,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do description: "Update the user's display and preferences.", operationId: "AccountController.update_credentials", security: [%{"oAuth" => ["write:accounts"]}], - requestBody: - Helpers.request_body("Parameters", AccountUpdateCredentialsRequest, required: true), + requestBody: request_body("Parameters", AccountUpdateCredentialsRequest, required: true), responses: %{ 200 => Operation.response("Account", "application/json", Account) } @@ -114,49 +114,31 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do operationId: "AccountController.statuses", description: "Statuses posted to the given account. Public (for public statuses only), or user token + `read:statuses` (for private statuses the user is authorized to see)", - parameters: [ - %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}, - Operation.parameter(:pinned, :query, BooleanLike, "Include only pinned statuses"), - Operation.parameter(:tagged, :query, :string, "With tag"), - Operation.parameter( - :only_media, - :query, - BooleanLike, - "Include only statuses with media attached" - ), - Operation.parameter( - :with_muted, - :query, - BooleanLike, - "Include statuses from muted acccounts." - ), - Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"), - Operation.parameter( - :exclude_visibilities, - :query, - %Schema{type: :array, items: VisibilityScope}, - "Exclude visibilities" - ), - Operation.parameter(:max_id, :query, :string, "Return statuses older than this ID"), - Operation.parameter( - :min_id, - :query, - :string, - "Return the oldest statuses newer than this ID" - ), - Operation.parameter( - :since_id, - :query, - :string, - "Return the newest statuses newer than this ID" - ), - Operation.parameter( - :limit, - :query, - %Schema{type: :integer, default: 20, maximum: 40}, - "Limit" - ) - ], + parameters: + [ + %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}, + Operation.parameter(:pinned, :query, BooleanLike, "Include only pinned statuses"), + Operation.parameter(:tagged, :query, :string, "With tag"), + Operation.parameter( + :only_media, + :query, + BooleanLike, + "Include only statuses with media attached" + ), + Operation.parameter( + :with_muted, + :query, + BooleanLike, + "Include statuses from muted acccounts." + ), + Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"), + Operation.parameter( + :exclude_visibilities, + :query, + %Schema{type: :array, items: VisibilityScope}, + "Exclude visibilities" + ) + ] ++ pagination_params(), responses: %{ 200 => Operation.response("Statuses", "application/json", StatusesResponse) } @@ -171,18 +153,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do security: [%{"oAuth" => ["read:accounts"]}], description: "Accounts which follow the given account, if network is not hidden by the account owner.", - parameters: [ - %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}, - Operation.parameter(:max_id, :query, :string, "Max ID"), - Operation.parameter(:min_id, :query, :string, "Mix ID"), - Operation.parameter(:since_id, :query, :string, "Since ID"), - Operation.parameter( - :limit, - :query, - %Schema{type: :integer, default: 20, maximum: 40}, - "Limit" - ) - ], + parameters: + [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}] ++ pagination_params(), responses: %{ 200 => Operation.response("Accounts", "application/json", AccountsResponse) } @@ -197,18 +169,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do security: [%{"oAuth" => ["read:accounts"]}], description: "Accounts which the given account is following, if network is not hidden by the account owner.", - parameters: [ - %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}, - Operation.parameter(:max_id, :query, :string, "Max ID"), - Operation.parameter(:min_id, :query, :string, "Mix ID"), - Operation.parameter(:since_id, :query, :string, "Since ID"), - Operation.parameter( - :limit, - :query, - %Schema{type: :integer, default: 20, maximum: 40}, - "Limit" - ) - ], + parameters: + [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}] ++ pagination_params(), responses: %{200 => Operation.response("Accounts", "application/json", AccountsResponse)} } end @@ -267,7 +229,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do summary: "Mute", operationId: "AccountController.mute", security: [%{"oAuth" => ["follow", "write:mutes"]}], - requestBody: Helpers.request_body("Parameters", AccountMuteRequest), + requestBody: request_body("Parameters", AccountMuteRequest), description: "Mute the given account. Clients should filter statuses and notifications from this account, if received (e.g. due to a boost in the Home timeline).", parameters: [ @@ -334,7 +296,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do summary: "Follows", operationId: "AccountController.follows", security: [%{"oAuth" => ["follow", "write:follows"]}], - requestBody: Helpers.request_body("Parameters", AccountFollowsRequest, required: true), + requestBody: request_body("Parameters", AccountFollowsRequest, required: true), responses: %{ 200 => Operation.response("Account", "application/json", Account) } -- cgit v1.2.3 From 16f4787bf7e4849192d999eb2177ca7e1a34fbc9 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 15 Apr 2020 16:51:37 +0400 Subject: Add a TODO note --- lib/pleroma/web/activity_pub/activity_pub.ex | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 1909ce097..5926a6cad 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1157,6 +1157,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do ) end + # TODO: when all endpoints migrated to OpenAPI compare `pinned` with `true` (boolean) only, + # the same for `restrict_media/2`, `restrict_replies/2`, 'restrict_reblogs/2' and `restrict_muted/2` defp restrict_pinned(query, %{"pinned" => pinned, "pinned_activity_ids" => ids}) when pinned in [true, "true", "1"] do from(activity in query, where: activity.id in ^ids) -- cgit v1.2.3 From 65f04b7806e342ed8967e5fa760e1509a776036e Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 15 Apr 2020 17:16:32 +0400 Subject: Fix credo warning --- lib/pleroma/web/activity_pub/activity_pub.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 5926a6cad..fa913a2aa 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1158,7 +1158,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end # TODO: when all endpoints migrated to OpenAPI compare `pinned` with `true` (boolean) only, - # the same for `restrict_media/2`, `restrict_replies/2`, 'restrict_reblogs/2' and `restrict_muted/2` + # the same for `restrict_media/2`, `restrict_replies/2`, 'restrict_reblogs/2' + # and `restrict_muted/2` + defp restrict_pinned(query, %{"pinned" => pinned, "pinned_activity_ids" => ids}) when pinned in [true, "true", "1"] do from(activity in query, where: activity.id in ^ids) -- cgit v1.2.3 From 163341857a726e8d74b6ddcd1230579e4c36a1b5 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Fri, 17 Apr 2020 19:27:22 +0400 Subject: Improve OpenAPI errors --- lib/pleroma/web/api_spec/render_error.ex | 220 +++++++++++++++++++++++++++++-- 1 file changed, 212 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/render_error.ex b/lib/pleroma/web/api_spec/render_error.ex index 9184c43b6..b5877ca9c 100644 --- a/lib/pleroma/web/api_spec/render_error.ex +++ b/lib/pleroma/web/api_spec/render_error.ex @@ -5,23 +5,227 @@ defmodule Pleroma.Web.ApiSpec.RenderError do @behaviour Plug - alias OpenApiSpex.Plug.JsonRenderError - alias Plug.Conn + import Plug.Conn, only: [put_status: 2] + import Phoenix.Controller, only: [json: 2] + import Pleroma.Web.Gettext @impl Plug def init(opts), do: opts @impl Plug - def call(%{private: %{open_api_spex: %{operation_id: "AccountController.create"}}} = conn, _) do + def call(conn, errors) do + errors = + Enum.map(errors, fn + %{name: nil} = err -> + %OpenApiSpex.Cast.Error{err | name: List.last(err.path)} + + err -> + err + end) + conn - |> Conn.put_status(:bad_request) - |> Phoenix.Controller.json(%{"error" => "Missing parameters"}) + |> put_status(:bad_request) + |> json(%{ + error: errors |> Enum.map(&message/1) |> Enum.join(" "), + errors: errors |> Enum.map(&render_error/1) + }) + end + + defp render_error(error) do + pointer = OpenApiSpex.path_to_string(error) + + %{ + title: "Invalid value", + source: %{ + pointer: pointer + }, + message: OpenApiSpex.Cast.Error.message(error) + } + end + + defp message(%{reason: :invalid_schema_type, type: type, name: name}) do + gettext("%{name} - Invalid schema.type. Got: %{type}.", + name: name, + type: inspect(type) + ) + end + + defp message(%{reason: :null_value, name: name} = error) do + case error.type do + nil -> + gettext("%{name} - null value.", name: name) + + type -> + gettext("%{name} - null value where %{type} expected.", + name: name, + type: type + ) + end + end + + defp message(%{reason: :all_of, meta: %{invalid_schema: invalid_schema}}) do + gettext( + "Failed to cast value as %{invalid_schema}. Value must be castable using `allOf` schemas listed.", + invalid_schema: invalid_schema + ) + end + + defp message(%{reason: :any_of, meta: %{failed_schemas: failed_schemas}}) do + gettext("Failed to cast value using any of: %{failed_schemas}.", + failed_schemas: failed_schemas + ) + end + + defp message(%{reason: :one_of, meta: %{failed_schemas: failed_schemas}}) do + gettext("Failed to cast value to one of: %{failed_schemas}.", failed_schemas: failed_schemas) end - def call(conn, reason) do - opts = JsonRenderError.init(reason) + defp message(%{reason: :min_length, length: length, name: name}) do + gettext("%{name} - String length is smaller than minLength: %{length}.", + name: name, + length: length + ) + end + + defp message(%{reason: :max_length, length: length, name: name}) do + gettext("%{name} - String length is larger than maxLength: %{length}.", + name: name, + length: length + ) + end + + defp message(%{reason: :unique_items, name: name}) do + gettext("%{name} - Array items must be unique.", name: name) + end + + defp message(%{reason: :min_items, length: min, value: array, name: name}) do + gettext("%{name} - Array length %{length} is smaller than minItems: %{min}.", + name: name, + length: length(array), + min: min + ) + end + + defp message(%{reason: :max_items, length: max, value: array, name: name}) do + gettext("%{name} - Array length %{length} is larger than maxItems: %{}.", + name: name, + length: length(array), + max: max + ) + end + + defp message(%{reason: :multiple_of, length: multiple, value: count, name: name}) do + gettext("%{name} - %{count} is not a multiple of %{multiple}.", + name: name, + count: count, + multiple: multiple + ) + end + + defp message(%{reason: :exclusive_max, length: max, value: value, name: name}) + when value >= max do + gettext("%{name} - %{value} is larger than exclusive maximum %{max}.", + name: name, + value: value, + max: max + ) + end + + defp message(%{reason: :maximum, length: max, value: value, name: name}) + when value > max do + gettext("%{name} - %{value} is larger than inclusive maximum %{max}.", + name: name, + value: value, + max: max + ) + end + + defp message(%{reason: :exclusive_multiple, length: min, value: value, name: name}) + when value <= min do + gettext("%{name} - %{value} is smaller than exclusive minimum %{min}.", + name: name, + value: value, + min: min + ) + end + + defp message(%{reason: :minimum, length: min, value: value, name: name}) + when value < min do + gettext("%{name} - %{value} is smaller than inclusive minimum %{min}.", + name: name, + value: value, + min: min + ) + end + + defp message(%{reason: :invalid_type, type: type, value: value, name: name}) do + gettext("%{name} - Invalid %{type}. Got: %{value}.", + name: name, + value: OpenApiSpex.TermType.type(value), + type: type + ) + end + + defp message(%{reason: :invalid_format, format: format, name: name}) do + gettext("%{name} - Invalid format. Expected %{format}.", name: name, format: inspect(format)) + end + + defp message(%{reason: :invalid_enum, name: name}) do + gettext("%{name} - Invalid value for enum.", name: name) + end + + defp message(%{reason: :polymorphic_failed, type: polymorphic_type}) do + gettext("Failed to cast to any schema in %{polymorphic_type}", + polymorphic_type: polymorphic_type + ) + end + + defp message(%{reason: :unexpected_field, name: name}) do + gettext("Unexpected field: %{name}.", name: safe_string(name)) + end + + defp message(%{reason: :no_value_for_discriminator, name: field}) do + gettext("Value used as discriminator for `%{field}` matches no schemas.", name: field) + end + + defp message(%{reason: :invalid_discriminator_value, name: field}) do + gettext("No value provided for required discriminator `%{field}`.", name: field) + end + + defp message(%{reason: :unknown_schema, name: name}) do + gettext("Unknown schema: %{name}.", name: name) + end + + defp message(%{reason: :missing_field, name: name}) do + gettext("Missing field: %{name}.", name: name) + end + + defp message(%{reason: :missing_header, name: name}) do + gettext("Missing header: %{name}.", name: name) + end + + defp message(%{reason: :invalid_header, name: name}) do + gettext("Invalid value for header: %{name}.", name: name) + end + + defp message(%{reason: :max_properties, meta: meta}) do + gettext( + "Object property count %{property_count} is greater than maxProperties: %{max_properties}.", + property_count: meta.property_count, + max_properties: meta.max_properties + ) + end + + defp message(%{reason: :min_properties, meta: meta}) do + gettext( + "Object property count %{property_count} is less than minProperties: %{min_properties}", + property_count: meta.property_count, + min_properties: meta.min_properties + ) + end - JsonRenderError.call(conn, opts) + defp safe_string(string) do + to_string(string) |> String.slice(0..39) end end -- cgit v1.2.3 From ed3974af248a1b201d2008f1a128ee53550ef78b Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 20 Apr 2020 18:39:05 +0400 Subject: Add OpenAPI spec for `AccountController.identity_proofs` operation --- lib/pleroma/web/api_spec/operations/account_operation.ex | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index fe44a917a..d3cebaf05 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -341,4 +341,16 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do } } end + + def identity_proofs_operation do + %Operation{ + tags: ["accounts"], + summary: "Identity proofs", + operationId: "AccountController.identity_proofs", + description: "Not implemented", + responses: %{ + 200 => Operation.response("Empry array", "application/json", %Schema{type: :array}) + } + } + end end -- cgit v1.2.3 From f685cbd30940b3fd92a2f6c8a161729bc2ceaab6 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Tue, 21 Apr 2020 16:29:19 +0300 Subject: Automatic checks of authentication / instance publicity. Definition of missing OAuth scopes in AdminAPIController. Refactoring. --- lib/pleroma/plugs/auth_expected_plug.ex | 17 --- lib/pleroma/plugs/ensure_authenticated_plug.ex | 8 +- .../plugs/ensure_public_or_authenticated_plug.ex | 6 +- .../plugs/expect_authenticated_check_plug.ex | 20 +++ .../expect_public_or_authenticated_check_plug.ex | 21 +++ lib/pleroma/plugs/oauth_scopes_plug.ex | 14 +- lib/pleroma/web/admin_api/admin_api_controller.ex | 36 +++-- lib/pleroma/web/fallback_redirect_controller.ex | 2 + lib/pleroma/web/masto_fe_controller.ex | 7 +- .../mastodon_api/controllers/account_controller.ex | 26 ++-- .../mastodon_api/controllers/auth_controller.ex | 4 +- .../controllers/conversation_controller.ex | 6 +- .../controllers/domain_block_controller.ex | 2 - .../mastodon_api/controllers/filter_controller.ex | 2 - .../controllers/follow_request_controller.ex | 2 - .../mastodon_api/controllers/list_controller.ex | 8 +- .../mastodon_api/controllers/marker_controller.ex | 2 +- .../controllers/mastodon_api_controller.ex | 2 - .../mastodon_api/controllers/media_controller.ex | 2 - .../controllers/notification_controller.ex | 2 - .../mastodon_api/controllers/poll_controller.ex | 2 - .../mastodon_api/controllers/report_controller.ex | 2 - .../controllers/scheduled_activity_controller.ex | 2 - .../mastodon_api/controllers/search_controller.ex | 2 - .../mastodon_api/controllers/status_controller.ex | 2 +- .../controllers/subscription_controller.ex | 2 +- .../controllers/timeline_controller.ex | 2 +- .../web/media_proxy/media_proxy_controller.ex | 1 + .../pleroma_api/controllers/account_controller.ex | 14 +- .../controllers/emoji_api_controller.ex | 16 ++- .../pleroma_api/controllers/mascot_controller.ex | 2 - .../controllers/pleroma_api_controller.ex | 16 ++- .../pleroma_api/controllers/scrobble_controller.ex | 2 - lib/pleroma/web/router.ex | 152 +++++++++++---------- .../web/twitter_api/controllers/util_controller.ex | 7 - .../web/twitter_api/twitter_api_controller.ex | 14 +- lib/pleroma/web/web.ex | 85 +++++++++--- 37 files changed, 302 insertions(+), 210 deletions(-) delete mode 100644 lib/pleroma/plugs/auth_expected_plug.ex create mode 100644 lib/pleroma/plugs/expect_authenticated_check_plug.ex create mode 100644 lib/pleroma/plugs/expect_public_or_authenticated_check_plug.ex (limited to 'lib') diff --git a/lib/pleroma/plugs/auth_expected_plug.ex b/lib/pleroma/plugs/auth_expected_plug.ex deleted file mode 100644 index f79597dc3..000000000 --- a/lib/pleroma/plugs/auth_expected_plug.ex +++ /dev/null @@ -1,17 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Plugs.AuthExpectedPlug do - import Plug.Conn - - def init(options), do: options - - def call(conn, _) do - put_private(conn, :auth_expected, true) - end - - def auth_expected?(conn) do - conn.private[:auth_expected] - end -end diff --git a/lib/pleroma/plugs/ensure_authenticated_plug.ex b/lib/pleroma/plugs/ensure_authenticated_plug.ex index 054d2297f..9c8f5597f 100644 --- a/lib/pleroma/plugs/ensure_authenticated_plug.ex +++ b/lib/pleroma/plugs/ensure_authenticated_plug.ex @@ -5,17 +5,21 @@ defmodule Pleroma.Plugs.EnsureAuthenticatedPlug do import Plug.Conn import Pleroma.Web.TranslationHelpers + alias Pleroma.User + use Pleroma.Web, :plug + def init(options) do options end - def call(%{assigns: %{user: %User{}}} = conn, _) do + @impl true + def perform(%{assigns: %{user: %User{}}} = conn, _) do conn end - def call(conn, options) do + def perform(conn, options) do perform = cond do options[:if_func] -> options[:if_func].() diff --git a/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex b/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex index d980ff13d..7265bb87a 100644 --- a/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex +++ b/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex @@ -5,14 +5,18 @@ defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug do import Pleroma.Web.TranslationHelpers import Plug.Conn + alias Pleroma.Config alias Pleroma.User + use Pleroma.Web, :plug + def init(options) do options end - def call(conn, _) do + @impl true + def perform(conn, _) do public? = Config.get!([:instance, :public]) case {public?, conn} do diff --git a/lib/pleroma/plugs/expect_authenticated_check_plug.ex b/lib/pleroma/plugs/expect_authenticated_check_plug.ex new file mode 100644 index 000000000..66b8d5de5 --- /dev/null +++ b/lib/pleroma/plugs/expect_authenticated_check_plug.ex @@ -0,0 +1,20 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Plugs.ExpectAuthenticatedCheckPlug do + @moduledoc """ + Marks `Pleroma.Plugs.EnsureAuthenticatedPlug` as expected to be executed later in plug chain. + + No-op plug which affects `Pleroma.Web` operation (is checked with `PlugHelper.plug_called?/2`). + """ + + use Pleroma.Web, :plug + + def init(options), do: options + + @impl true + def perform(conn, _) do + conn + end +end diff --git a/lib/pleroma/plugs/expect_public_or_authenticated_check_plug.ex b/lib/pleroma/plugs/expect_public_or_authenticated_check_plug.ex new file mode 100644 index 000000000..ba0ef76bd --- /dev/null +++ b/lib/pleroma/plugs/expect_public_or_authenticated_check_plug.ex @@ -0,0 +1,21 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug do + @moduledoc """ + Marks `Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug` as expected to be executed later in plug + chain. + + No-op plug which affects `Pleroma.Web` operation (is checked with `PlugHelper.plug_called?/2`). + """ + + use Pleroma.Web, :plug + + def init(options), do: options + + @impl true + def perform(conn, _) do + conn + end +end diff --git a/lib/pleroma/plugs/oauth_scopes_plug.ex b/lib/pleroma/plugs/oauth_scopes_plug.ex index 66f48c28c..a61582566 100644 --- a/lib/pleroma/plugs/oauth_scopes_plug.ex +++ b/lib/pleroma/plugs/oauth_scopes_plug.ex @@ -7,15 +7,12 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do import Pleroma.Web.Gettext alias Pleroma.Config - alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug - alias Pleroma.Plugs.PlugHelper use Pleroma.Web, :plug - @behaviour Plug - def init(%{scopes: _} = options), do: options + @impl true def perform(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do op = options[:op] || :| token = assigns[:token] @@ -34,7 +31,6 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do conn |> assign(:user, nil) |> assign(:token, nil) - |> maybe_perform_instance_privacy_check(options) true -> missing_scopes = scopes -- matched_scopes @@ -71,12 +67,4 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do scopes end end - - defp maybe_perform_instance_privacy_check(%Plug.Conn{} = conn, options) do - if options[:skip_instance_privacy_check] do - conn - else - EnsurePublicOrAuthenticatedPlug.call(conn, []) - end - end end diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 9c79310c0..816c11e01 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -48,6 +48,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do %{scopes: ["write:accounts"], admin: true} when action in [ :get_password_reset, + :force_password_reset, :user_delete, :users_create, :user_toggle_activation, @@ -56,7 +57,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do :tag_users, :untag_users, :right_add, + :right_add_multiple, :right_delete, + :right_delete_multiple, :update_user_credentials ] ) @@ -84,13 +87,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do plug( OAuthScopesPlug, %{scopes: ["write:reports"], admin: true} - when action in [:reports_update] + when action in [:reports_update, :report_notes_create, :report_notes_delete] ) plug( OAuthScopesPlug, %{scopes: ["read:statuses"], admin: true} - when action == :list_user_statuses + when action in [:list_statuses, :list_user_statuses, :list_instance_statuses] ) plug( @@ -102,13 +105,30 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do plug( OAuthScopesPlug, %{scopes: ["read"], admin: true} - when action in [:config_show, :list_log, :stats] + when action in [ + :config_show, + :list_log, + :stats, + :relay_list, + :config_descriptions, + :need_reboot + ] ) plug( OAuthScopesPlug, %{scopes: ["write"], admin: true} - when action == :config_update + when action in [ + :restart, + :config_update, + :resend_confirmation_email, + :confirm_email, + :oauth_app_create, + :oauth_app_list, + :oauth_app_update, + :oauth_app_delete, + :reload_emoji + ] ) action_fallback(:errors) @@ -1103,25 +1123,25 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do |> json(%{"status_visibility" => count}) end - def errors(conn, {:error, :not_found}) do + defp errors(conn, {:error, :not_found}) do conn |> put_status(:not_found) |> json(dgettext("errors", "Not found")) end - def errors(conn, {:error, reason}) do + defp errors(conn, {:error, reason}) do conn |> put_status(:bad_request) |> json(reason) end - def errors(conn, {:param_cast, _}) do + defp errors(conn, {:param_cast, _}) do conn |> put_status(:bad_request) |> json(dgettext("errors", "Invalid parameters")) end - def errors(conn, _) do + defp errors(conn, _) do conn |> put_status(:internal_server_error) |> json(dgettext("errors", "Something went wrong")) diff --git a/lib/pleroma/web/fallback_redirect_controller.ex b/lib/pleroma/web/fallback_redirect_controller.ex index c13518030..0d9d578fc 100644 --- a/lib/pleroma/web/fallback_redirect_controller.ex +++ b/lib/pleroma/web/fallback_redirect_controller.ex @@ -4,7 +4,9 @@ defmodule Fallback.RedirectController do use Pleroma.Web, :controller + require Logger + alias Pleroma.User alias Pleroma.Web.Metadata diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex index 557cde328..9a2ec517a 100644 --- a/lib/pleroma/web/masto_fe_controller.ex +++ b/lib/pleroma/web/masto_fe_controller.ex @@ -13,11 +13,14 @@ defmodule Pleroma.Web.MastoFEController do # Note: :index action handles attempt of unauthenticated access to private instance with redirect plug( OAuthScopesPlug, - %{scopes: ["read"], fallback: :proceed_unauthenticated, skip_instance_privacy_check: true} + %{scopes: ["read"], fallback: :proceed_unauthenticated} when action == :index ) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action not in [:index, :manifest]) + plug( + :skip_plug, + Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :manifest] + ) @doc "GET /web/*path" def index(%{assigns: %{user: user, token: token}} = conn, _params) diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index e8e59ac66..9b8cc0d0d 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -26,12 +26,24 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do alias Pleroma.Web.OAuth.Token alias Pleroma.Web.TwitterAPI.TwitterAPI - plug(:skip_plug, OAuthScopesPlug when action == :identity_proofs) + plug(:skip_plug, OAuthScopesPlug when action in [:create, :identity_proofs]) + + plug( + :skip_plug, + Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug + when action in [:create, :show, :statuses] + ) plug( OAuthScopesPlug, %{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]} - when action == :show + when action in [:show, :endorsements] + ) + + plug( + OAuthScopesPlug, + %{fallback: :proceed_unauthenticated, scopes: ["read:statuses"]} + when action == :statuses ) plug( @@ -56,21 +68,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug(OAuthScopesPlug, %{scopes: ["read:follows"]} when action == :relationships) - # Note: :follows (POST /api/v1/follows) is the same as :follow, consider removing :follows plug( OAuthScopesPlug, - %{scopes: ["follow", "write:follows"]} when action in [:follows, :follow, :unfollow] + %{scopes: ["follow", "write:follows"]} when action in [:follow_by_uri, :follow, :unfollow] ) plug(OAuthScopesPlug, %{scopes: ["follow", "read:mutes"]} when action == :mutes) plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute]) - plug( - Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug - when action not in [:create, :show, :statuses] - ) - @relationship_actions [:follow, :unfollow] @needs_account ~W(followers following lists follow unfollow mute unmute block unblock)a @@ -356,7 +362,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "POST /api/v1/follows" - def follows(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do + def follow_by_uri(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)}, {_, true} <- {:followed, follower.id != followed.id}, {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex index 37b389382..753b3db3e 100644 --- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex @@ -13,10 +13,10 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) - @local_mastodon_name "Mastodon-Local" - plug(Pleroma.Plugs.RateLimiter, [name: :password_reset] when action == :password_reset) + @local_mastodon_name "Mastodon-Local" + @doc "GET /web/login" def login(%{assigns: %{user: %User{}}} = conn, _params) do redirect(conn, to: local_mastodon_root_path(conn)) diff --git a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex index 7c9b11bf1..c44641526 100644 --- a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex @@ -14,9 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action == :index) - plug(OAuthScopesPlug, %{scopes: ["write:conversations"]} when action == :read) - - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) + plug(OAuthScopesPlug, %{scopes: ["write:conversations"]} when action != :index) @doc "GET /api/v1/conversations" def index(%{assigns: %{user: user}} = conn, params) do @@ -28,7 +26,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController do end @doc "POST /api/v1/conversations/:id/read" - def read(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do + def mark_as_read(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do with %Participation{} = participation <- Repo.get_by(Participation, id: participation_id, user_id: user.id), {:ok, participation} <- Participation.mark_as_read(participation) do diff --git a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex index 84de79413..c4fa383f2 100644 --- a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex @@ -21,8 +21,6 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do %{scopes: ["follow", "write:blocks"]} when action != :index ) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) - @doc "GET /api/v1/domain_blocks" def index(%{assigns: %{user: user}} = conn, _) do json(conn, Map.get(user, :domain_blocks, [])) diff --git a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex index 7b0b937a2..7fd0562c9 100644 --- a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex @@ -17,8 +17,6 @@ defmodule Pleroma.Web.MastodonAPI.FilterController do %{scopes: ["write:filters"]} when action not in @oauth_read_actions ) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) - @doc "GET /api/v1/filters" def index(%{assigns: %{user: user}} = conn, _) do filters = Filter.get_filters(user) diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex index 1ca86f63f..25f2269b9 100644 --- a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex @@ -21,8 +21,6 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do %{scopes: ["follow", "write:follows"]} when action != :index ) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) - @doc "GET /api/v1/follow_requests" def index(%{assigns: %{user: followed}} = conn, _params) do follow_requests = User.get_follow_requests(followed) diff --git a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex index dac4daa7b..bfe856025 100644 --- a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex @@ -11,16 +11,16 @@ defmodule Pleroma.Web.MastodonAPI.ListController do plug(:list_by_id_and_user when action not in [:index, :create]) - plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action in [:index, :show, :list_accounts]) + @oauth_read_actions [:index, :show, :list_accounts] + + plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action in @oauth_read_actions) plug( OAuthScopesPlug, %{scopes: ["write:lists"]} - when action in [:create, :update, :delete, :add_to_list, :remove_from_list] + when action not in @oauth_read_actions ) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) - action_fallback(Pleroma.Web.MastodonAPI.FallbackController) # GET /api/v1/lists diff --git a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex index 58e8a30c2..9f9d4574e 100644 --- a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.MarkerController do ) plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action == :upsert) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) # GET /api/v1/markers diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index ac8c18f24..f0492b189 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -17,8 +17,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do plug(:skip_plug, Pleroma.Plugs.OAuthScopesPlug when action in [:empty_array, :empty_object]) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) - action_fallback(Pleroma.Web.MastodonAPI.FallbackController) def empty_array(conn, _) do diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex index 2b6f00952..e36751220 100644 --- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex @@ -15,8 +15,6 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do plug(OAuthScopesPlug, %{scopes: ["write:media"]}) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) - @doc "POST /api/v1/media" def create(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do with {:ok, object} <- diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex index 7fb536b09..311405277 100644 --- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -20,8 +20,6 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action not in @oauth_read_actions) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) - # GET /api/v1/notifications def index(conn, %{"account_id" => account_id} = params) do case Pleroma.User.get_cached_by_id(account_id) do diff --git a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex index d9f894118..af9b66eff 100644 --- a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex @@ -22,8 +22,6 @@ defmodule Pleroma.Web.MastodonAPI.PollController do plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action == :vote) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) - @doc "GET /api/v1/polls/:id" def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60), diff --git a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex index f5782be13..9fbaa7bd1 100644 --- a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex @@ -11,8 +11,6 @@ defmodule Pleroma.Web.MastodonAPI.ReportController do plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) - @doc "POST /api/v1/reports" def create(%{assigns: %{user: user}} = conn, params) do with {:ok, activity} <- Pleroma.Web.CommonAPI.report(user, params) do diff --git a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex index e1e6bd89b..899b78873 100644 --- a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex @@ -18,8 +18,6 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in @oauth_read_actions) plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action not in @oauth_read_actions) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) - action_fallback(Pleroma.Web.MastodonAPI.FallbackController) @doc "GET /api/v1/scheduled_statuses" diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index c258742dd..b54c56967 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -21,8 +21,6 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do # Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search) plug(OAuthScopesPlug, %{scopes: ["read:search"], fallback: :proceed_unauthenticated}) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) - plug(RateLimiter, [name: :search] when action in [:search, :search2, :account_search]) def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 397dd10e3..eade83aaf 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -77,7 +77,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do %{scopes: ["write:bookmarks"]} when action in [:bookmark, :unbookmark] ) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action not in [:index, :show]) + plug(:skip_plug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show]) @rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete)a diff --git a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex index 4647c1f96..d184ea1d0 100644 --- a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex @@ -12,7 +12,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do action_fallback(:errors) plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["push"]}) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) + plug(:restrict_push_enabled) # Creates PushSubscription diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index b3c58005e..891f924bc 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -26,7 +26,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct]) plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :list) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action != :public) + plug(:skip_plug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action == :public) plug(:put_view, Pleroma.Web.MastodonAPI.StatusView) diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 1a09ac62a..4657a4383 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do use Pleroma.Web, :controller + alias Pleroma.ReverseProxy alias Pleroma.Web.MediaProxy diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex index 60405fbff..d6ffdcbe4 100644 --- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex @@ -17,6 +17,13 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do require Pleroma.Constants + plug(:skip_plug, OAuthScopesPlug when action == :confirmation_resend) + + plug( + :skip_plug, + Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action == :confirmation_resend + ) + plug( OAuthScopesPlug, %{scopes: ["follow", "write:follows"]} when action in [:subscribe, :unsubscribe] @@ -35,13 +42,8 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do plug(OAuthScopesPlug, %{scopes: ["read:favourites"]} when action == :favourites) - # An extra safety measure for possible actions not guarded by OAuth permissions specification - plug( - Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug - when action != :confirmation_resend - ) - plug(RateLimiter, [name: :account_confirmation_resend] when action == :confirmation_resend) + plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe]) plug(:put_view, Pleroma.Web.MastodonAPI.AccountView) diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index 03e95e020..e01825b48 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -1,6 +1,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do use Pleroma.Web, :controller + alias Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug alias Pleroma.Plugs.OAuthScopesPlug require Logger @@ -11,17 +12,20 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do when action in [ :create, :delete, - :download_from, - :list_from, + :save_from, :import_from_fs, :update_file, :update_metadata ] ) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) + plug( + :skip_plug, + [OAuthScopesPlug, ExpectPublicOrAuthenticatedCheckPlug] + when action in [:download_shared, :list_packs, :list_from] + ) - def emoji_dir_path do + defp emoji_dir_path do Path.join( Pleroma.Config.get!([:instance, :static_dir]), "emoji" @@ -212,13 +216,13 @@ keeping it in cache for #{div(cache_ms, 1000)}s") end @doc """ - An admin endpoint to request downloading a pack named `pack_name` from the instance + An admin endpoint to request downloading and storing a pack named `pack_name` from the instance `instance_address`. If the requested instance's admin chose to share the pack, it will be downloaded from that instance, otherwise it will be downloaded from the fallback source, if there is one. """ - def download_from(conn, %{"instance_address" => address, "pack_name" => name} = data) do + def save_from(conn, %{"instance_address" => address, "pack_name" => name} = data) do address = String.trim(address) if shareable_packs_available(address) do diff --git a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex index d9c1c8636..d4e0d8b7c 100644 --- a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex @@ -12,8 +12,6 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action == :show) plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action != :show) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) - @doc "GET /api/v1/pleroma/mascot" def show(%{assigns: %{user: user}} = conn, _params) do json(conn, User.get_mascot(user)) diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index fe1b97a20..7a65697e8 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -34,12 +34,14 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do plug( OAuthScopesPlug, - %{scopes: ["write:conversations"]} when action in [:update_conversation, :read_conversations] + %{scopes: ["write:conversations"]} + when action in [:update_conversation, :mark_conversations_as_read] ) - plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :read_notification) - - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) + plug( + OAuthScopesPlug, + %{scopes: ["write:notifications"]} when action == :mark_notifications_as_read + ) def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id} = params) do with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id), @@ -167,7 +169,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do end end - def read_conversations(%{assigns: %{user: user}} = conn, _params) do + def mark_conversations_as_read(%{assigns: %{user: user}} = conn, _params) do with {:ok, _, participations} <- Participation.mark_all_as_read(user) do conn |> add_link_headers(participations) @@ -176,7 +178,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do end end - def read_notification(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do + def mark_notifications_as_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do with {:ok, notification} <- Notification.read_one(user, notification_id) do conn |> put_view(NotificationView) @@ -189,7 +191,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do end end - def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id} = params) do + def mark_notifications_as_read(%{assigns: %{user: user}} = conn, %{"max_id" => max_id} = params) do with notifications <- Notification.set_read_up_to(user, max_id) do notifications = Enum.take(notifications, 80) diff --git a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex index 4463ec477..c81e8535e 100644 --- a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex @@ -16,8 +16,6 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleController do plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :user_scrobbles) plug(OAuthScopesPlug, %{scopes: ["write"]} when action != :user_scrobbles) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) - def new_scrobble(%{assigns: %{user: user}} = conn, %{"title" => _} = params) do params = if !params["length"] do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 153802a43..04c1c5941 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -16,6 +16,14 @@ defmodule Pleroma.Web.Router do plug(Pleroma.Plugs.UserEnabledPlug) end + pipeline :expect_authentication do + plug(Pleroma.Plugs.ExpectAuthenticatedCheckPlug) + end + + pipeline :expect_public_instance_or_authentication do + plug(Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug) + end + pipeline :authenticate do plug(Pleroma.Plugs.OAuthPlug) plug(Pleroma.Plugs.BasicAuthDecoderPlug) @@ -39,20 +47,22 @@ defmodule Pleroma.Web.Router do end pipeline :api do + plug(:expect_public_instance_or_authentication) plug(:base_api) plug(:after_auth) plug(Pleroma.Plugs.IdempotencyPlug) end pipeline :authenticated_api do + plug(:expect_authentication) plug(:base_api) - plug(Pleroma.Plugs.AuthExpectedPlug) plug(:after_auth) plug(Pleroma.Plugs.EnsureAuthenticatedPlug) plug(Pleroma.Plugs.IdempotencyPlug) end pipeline :admin_api do + plug(:expect_authentication) plug(:base_api) plug(Pleroma.Plugs.AdminSecretAuthenticationPlug) plug(:after_auth) @@ -200,24 +210,28 @@ defmodule Pleroma.Web.Router do end scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do + # Modifying packs scope "/packs" do - # Modifying packs pipe_through(:admin_api) post("/import_from_fs", EmojiAPIController, :import_from_fs) - post("/:pack_name/update_file", EmojiAPIController, :update_file) post("/:pack_name/update_metadata", EmojiAPIController, :update_metadata) put("/:name", EmojiAPIController, :create) delete("/:name", EmojiAPIController, :delete) - post("/download_from", EmojiAPIController, :download_from) - post("/list_from", EmojiAPIController, :list_from) + + # Note: /download_from downloads and saves to instance, not to requester + post("/download_from", EmojiAPIController, :save_from) end + # Pack info / downloading scope "/packs" do - # Pack info / downloading get("/", EmojiAPIController, :list_packs) get("/:name/download_shared/", EmojiAPIController, :download_shared) + get("/list_from", EmojiAPIController, :list_from) + + # Deprecated: POST /api/pleroma/emoji/packs/list_from (use GET instead) + post("/list_from", EmojiAPIController, :list_from) end end @@ -277,7 +291,7 @@ defmodule Pleroma.Web.Router do get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses) get("/conversations/:id", PleromaAPIController, :conversation) - post("/conversations/read", PleromaAPIController, :read_conversations) + post("/conversations/read", PleromaAPIController, :mark_conversations_as_read) end scope [] do @@ -286,7 +300,7 @@ defmodule Pleroma.Web.Router do patch("/conversations/:id", PleromaAPIController, :update_conversation) put("/statuses/:id/reactions/:emoji", PleromaAPIController, :react_with_emoji) delete("/statuses/:id/reactions/:emoji", PleromaAPIController, :unreact_with_emoji) - post("/notifications/read", PleromaAPIController, :read_notification) + post("/notifications/read", PleromaAPIController, :mark_notifications_as_read) patch("/accounts/update_avatar", AccountController, :update_avatar) patch("/accounts/update_banner", AccountController, :update_banner) @@ -322,53 +336,81 @@ defmodule Pleroma.Web.Router do pipe_through(:authenticated_api) get("/accounts/verify_credentials", AccountController, :verify_credentials) + patch("/accounts/update_credentials", AccountController, :update_credentials) get("/accounts/relationships", AccountController, :relationships) - get("/accounts/:id/lists", AccountController, :lists) get("/accounts/:id/identity_proofs", AccountController, :identity_proofs) - - get("/follow_requests", FollowRequestController, :index) + get("/endorsements", AccountController, :endorsements) get("/blocks", AccountController, :blocks) get("/mutes", AccountController, :mutes) - get("/timelines/home", TimelineController, :home) - get("/timelines/direct", TimelineController, :direct) + post("/follows", AccountController, :follow_by_uri) + post("/accounts/:id/follow", AccountController, :follow) + post("/accounts/:id/unfollow", AccountController, :unfollow) + post("/accounts/:id/block", AccountController, :block) + post("/accounts/:id/unblock", AccountController, :unblock) + post("/accounts/:id/mute", AccountController, :mute) + post("/accounts/:id/unmute", AccountController, :unmute) - get("/favourites", StatusController, :favourites) - get("/bookmarks", StatusController, :bookmarks) + get("/conversations", ConversationController, :index) + post("/conversations/:id/read", ConversationController, :mark_as_read) + + get("/domain_blocks", DomainBlockController, :index) + post("/domain_blocks", DomainBlockController, :create) + delete("/domain_blocks", DomainBlockController, :delete) + + get("/filters", FilterController, :index) + + post("/filters", FilterController, :create) + get("/filters/:id", FilterController, :show) + put("/filters/:id", FilterController, :update) + delete("/filters/:id", FilterController, :delete) + + get("/follow_requests", FollowRequestController, :index) + post("/follow_requests/:id/authorize", FollowRequestController, :authorize) + post("/follow_requests/:id/reject", FollowRequestController, :reject) + + get("/lists", ListController, :index) + get("/lists/:id", ListController, :show) + get("/lists/:id/accounts", ListController, :list_accounts) + + delete("/lists/:id", ListController, :delete) + post("/lists", ListController, :create) + put("/lists/:id", ListController, :update) + post("/lists/:id/accounts", ListController, :add_to_list) + delete("/lists/:id/accounts", ListController, :remove_from_list) + + get("/markers", MarkerController, :index) + post("/markers", MarkerController, :upsert) + + post("/media", MediaController, :create) + put("/media/:id", MediaController, :update) get("/notifications", NotificationController, :index) get("/notifications/:id", NotificationController, :show) + post("/notifications/:id/dismiss", NotificationController, :dismiss) post("/notifications/clear", NotificationController, :clear) delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple) # Deprecated: was removed in Mastodon v3, use `/notifications/:id/dismiss` instead post("/notifications/dismiss", NotificationController, :dismiss) - get("/scheduled_statuses", ScheduledActivityController, :index) - get("/scheduled_statuses/:id", ScheduledActivityController, :show) - - get("/lists", ListController, :index) - get("/lists/:id", ListController, :show) - get("/lists/:id/accounts", ListController, :list_accounts) - - get("/domain_blocks", DomainBlockController, :index) - - get("/filters", FilterController, :index) + post("/polls/:id/votes", PollController, :vote) - get("/suggestions", SuggestionController, :index) + post("/reports", ReportController, :create) - get("/conversations", ConversationController, :index) - post("/conversations/:id/read", ConversationController, :read) + get("/scheduled_statuses", ScheduledActivityController, :index) + get("/scheduled_statuses/:id", ScheduledActivityController, :show) - get("/endorsements", AccountController, :endorsements) + put("/scheduled_statuses/:id", ScheduledActivityController, :update) + delete("/scheduled_statuses/:id", ScheduledActivityController, :delete) - patch("/accounts/update_credentials", AccountController, :update_credentials) + get("/favourites", StatusController, :favourites) + get("/bookmarks", StatusController, :bookmarks) post("/statuses", StatusController, :create) delete("/statuses/:id", StatusController, :delete) - post("/statuses/:id/reblog", StatusController, :reblog) post("/statuses/:id/unreblog", StatusController, :unreblog) post("/statuses/:id/favourite", StatusController, :favourite) @@ -380,49 +422,15 @@ defmodule Pleroma.Web.Router do post("/statuses/:id/mute", StatusController, :mute_conversation) post("/statuses/:id/unmute", StatusController, :unmute_conversation) - put("/scheduled_statuses/:id", ScheduledActivityController, :update) - delete("/scheduled_statuses/:id", ScheduledActivityController, :delete) - - post("/polls/:id/votes", PollController, :vote) - - post("/media", MediaController, :create) - put("/media/:id", MediaController, :update) - - delete("/lists/:id", ListController, :delete) - post("/lists", ListController, :create) - put("/lists/:id", ListController, :update) - - post("/lists/:id/accounts", ListController, :add_to_list) - delete("/lists/:id/accounts", ListController, :remove_from_list) - - post("/filters", FilterController, :create) - get("/filters/:id", FilterController, :show) - put("/filters/:id", FilterController, :update) - delete("/filters/:id", FilterController, :delete) - - post("/reports", ReportController, :create) - - post("/follows", AccountController, :follows) - post("/accounts/:id/follow", AccountController, :follow) - post("/accounts/:id/unfollow", AccountController, :unfollow) - post("/accounts/:id/block", AccountController, :block) - post("/accounts/:id/unblock", AccountController, :unblock) - post("/accounts/:id/mute", AccountController, :mute) - post("/accounts/:id/unmute", AccountController, :unmute) - - post("/follow_requests/:id/authorize", FollowRequestController, :authorize) - post("/follow_requests/:id/reject", FollowRequestController, :reject) - - post("/domain_blocks", DomainBlockController, :create) - delete("/domain_blocks", DomainBlockController, :delete) - post("/push/subscription", SubscriptionController, :create) get("/push/subscription", SubscriptionController, :get) put("/push/subscription", SubscriptionController, :update) delete("/push/subscription", SubscriptionController, :delete) - get("/markers", MarkerController, :index) - post("/markers", MarkerController, :upsert) + get("/suggestions", SuggestionController, :index) + + get("/timelines/home", TimelineController, :home) + get("/timelines/direct", TimelineController, :direct) end scope "/api/web", Pleroma.Web do @@ -507,7 +515,11 @@ defmodule Pleroma.Web.Router do get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens) delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token) - post("/qvitter/statuses/notifications/read", TwitterAPI.Controller, :notifications_read) + post( + "/qvitter/statuses/notifications/read", + TwitterAPI.Controller, + :mark_notifications_as_read + ) end pipeline :ostatus do diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 537f9f778..9a4c39fa9 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -25,13 +25,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do when action == :follow_import ) - # Note: follower can submit the form (with password auth) not being signed in (having no token) - plug( - OAuthScopesPlug, - %{fallback: :proceed_unauthenticated, scopes: ["follow", "write:follows"]} - when action == :do_remote_follow - ) - plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks_import) plug( diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 31adc2817..55228616a 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -13,12 +13,13 @@ defmodule Pleroma.Web.TwitterAPI.Controller do require Logger - plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read) + plug( + OAuthScopesPlug, + %{scopes: ["write:notifications"]} when action == :mark_notifications_as_read + ) plug(:skip_plug, OAuthScopesPlug when action in [:oauth_tokens, :revoke_token]) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) - action_fallback(:errors) def confirm_email(conn, %{"user_id" => uid, "token" => token}) do @@ -64,7 +65,10 @@ defmodule Pleroma.Web.TwitterAPI.Controller do |> send_resp(status, json) end - def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest_id} = params) do + def mark_notifications_as_read( + %{assigns: %{user: user}} = conn, + %{"latest_id" => latest_id} = params + ) do Notification.set_read_up_to(user, latest_id) notifications = Notification.for_user(user, params) @@ -75,7 +79,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do |> render("index.json", %{notifications: notifications, for: user}) end - def notifications_read(%{assigns: %{user: _user}} = conn, _) do + def mark_notifications_as_read(%{assigns: %{user: _user}} = conn, _) do bad_request_reply(conn, "You need to specify latest_id") end diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex index bf48ce26c..ec04c05f0 100644 --- a/lib/pleroma/web/web.ex +++ b/lib/pleroma/web/web.ex @@ -2,6 +2,11 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only +defmodule Pleroma.Web.Plug do + # Substitute for `call/2` which is defined with `use Pleroma.Web, :plug` + @callback perform(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t() +end + defmodule Pleroma.Web do @moduledoc """ A module that keeps using definitions for controllers, @@ -20,44 +25,79 @@ defmodule Pleroma.Web do below. """ + alias Pleroma.Plugs.EnsureAuthenticatedPlug + alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug + alias Pleroma.Plugs.ExpectAuthenticatedCheckPlug + alias Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Plugs.PlugHelper + def controller do quote do use Phoenix.Controller, namespace: Pleroma.Web import Plug.Conn + import Pleroma.Web.Gettext import Pleroma.Web.Router.Helpers import Pleroma.Web.TranslationHelpers - alias Pleroma.Plugs.PlugHelper - plug(:set_put_layout) defp set_put_layout(conn, _) do put_layout(conn, Pleroma.Config.get(:app_layout, "app.html")) end - # Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain - defp skip_plug(conn, plug_module) do - try do - plug_module.skip_plug(conn) - rescue - UndefinedFunctionError -> - raise "#{plug_module} is not skippable. Append `use Pleroma.Web, :plug` to its code." - end + # Marks plugs intentionally skipped and blocks their execution if present in plugs chain + defp skip_plug(conn, plug_modules) do + plug_modules + |> List.wrap() + |> Enum.reduce( + conn, + fn plug_module, conn -> + try do + plug_module.skip_plug(conn) + rescue + UndefinedFunctionError -> + raise "`#{plug_module}` is not skippable. Append `use Pleroma.Web, :plug` to its code." + end + end + ) end # Executed just before actual controller action, invokes before-action hooks (callbacks) defp action(conn, params) do - with %Plug.Conn{halted: false} <- maybe_halt_on_missing_oauth_scopes_check(conn) do + with %Plug.Conn{halted: false} <- maybe_perform_public_or_authenticated_check(conn), + %Plug.Conn{halted: false} <- maybe_perform_authenticated_check(conn), + %Plug.Conn{halted: false} <- maybe_halt_on_missing_oauth_scopes_check(conn) do super(conn, params) end end + # Ensures instance is public -or- user is authenticated if such check was scheduled + defp maybe_perform_public_or_authenticated_check(conn) do + if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) do + EnsurePublicOrAuthenticatedPlug.call(conn, %{}) + else + conn + end + end + + # Ensures user is authenticated if such check was scheduled + # Note: runs prior to action even if it was already executed earlier in plug chain + # (since OAuthScopesPlug has option of proceeding unauthenticated) + defp maybe_perform_authenticated_check(conn) do + if PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug) do + EnsureAuthenticatedPlug.call(conn, %{}) + else + conn + end + end + # Halts if authenticated API action neither performs nor explicitly skips OAuth scopes check defp maybe_halt_on_missing_oauth_scopes_check(conn) do - if Pleroma.Plugs.AuthExpectedPlug.auth_expected?(conn) && - not PlugHelper.plug_called_or_skipped?(conn, Pleroma.Plugs.OAuthScopesPlug) do + if PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug) and + not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do conn |> render_error( :forbidden, @@ -132,7 +172,8 @@ defmodule Pleroma.Web do def plug do quote do - alias Pleroma.Plugs.PlugHelper + @behaviour Pleroma.Web.Plug + @behaviour Plug @doc """ Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain. @@ -146,14 +187,22 @@ defmodule Pleroma.Web do end @impl Plug - @doc "If marked as skipped, returns `conn`, and calls `perform/2` otherwise." + @doc """ + If marked as skipped, returns `conn`, otherwise calls `perform/2`. + Note: multiple invocations of the same plug (with different or same options) are allowed. + """ def call(%Plug.Conn{} = conn, options) do if PlugHelper.plug_skipped?(conn, __MODULE__) do conn else - conn - |> PlugHelper.append_to_private_list(PlugHelper.called_plugs_list_id(), __MODULE__) - |> perform(options) + conn = + PlugHelper.append_to_private_list( + conn, + PlugHelper.called_plugs_list_id(), + __MODULE__ + ) + + apply(__MODULE__, :perform, [conn, options]) end end end -- cgit v1.2.3 From f0238d010a61ab935b61beebd5674593a75f17dc Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 21 Apr 2020 23:30:24 +0400 Subject: Improve OpenAPI schema - Removes unneeded wrapping in examples - Adds `:format` attributes --- lib/pleroma/web/api_spec/schemas/account.ex | 152 ++++++++------- .../web/api_spec/schemas/account_create_request.ex | 6 +- .../api_spec/schemas/account_create_response.ex | 12 +- lib/pleroma/web/api_spec/schemas/account_emoji.ex | 18 +- lib/pleroma/web/api_spec/schemas/account_field.ex | 14 +- .../api_spec/schemas/account_field_attribute.ex | 6 +- .../api_spec/schemas/account_follows_request.ex | 2 +- .../web/api_spec/schemas/account_relationship.ex | 26 ++- .../web/api_spec/schemas/app_create_request.ex | 6 +- .../web/api_spec/schemas/app_create_response.ex | 4 +- lib/pleroma/web/api_spec/schemas/list.ex | 6 +- lib/pleroma/web/api_spec/schemas/status.ex | 206 ++++++++++----------- 12 files changed, 225 insertions(+), 233 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index beb093182..3634a7c76 100644 --- a/lib/pleroma/web/api_spec/schemas/account.ex +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -17,8 +17,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do type: :object, properties: %{ acct: %Schema{type: :string}, - avatar_static: %Schema{type: :string}, - avatar: %Schema{type: :string}, + avatar_static: %Schema{type: :string, format: :uri}, + avatar: %Schema{type: :string, format: :uri}, bot: %Schema{type: :boolean}, created_at: %Schema{type: :string, format: "date-time"}, display_name: %Schema{type: :string}, @@ -27,13 +27,13 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do follow_requests_count: %Schema{type: :integer}, followers_count: %Schema{type: :integer}, following_count: %Schema{type: :integer}, - header_static: %Schema{type: :string}, - header: %Schema{type: :string}, + header_static: %Schema{type: :string, format: :uri}, + header: %Schema{type: :string, format: :uri}, id: %Schema{type: :string}, locked: %Schema{type: :boolean}, - note: %Schema{type: :string}, + note: %Schema{type: :string, format: :html}, statuses_count: %Schema{type: :integer}, - url: %Schema{type: :string}, + url: %Schema{type: :string, format: :uri}, username: %Schema{type: :string}, pleroma: %Schema{ type: :object, @@ -104,80 +104,78 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do } }, example: %{ - "JSON" => %{ - "acct" => "foobar", - "avatar" => "https://mypleroma.com/images/avi.png", - "avatar_static" => "https://mypleroma.com/images/avi.png", - "bot" => false, - "created_at" => "2020-03-24T13:05:58.000Z", - "display_name" => "foobar", - "emojis" => [], + "acct" => "foobar", + "avatar" => "https://mypleroma.com/images/avi.png", + "avatar_static" => "https://mypleroma.com/images/avi.png", + "bot" => false, + "created_at" => "2020-03-24T13:05:58.000Z", + "display_name" => "foobar", + "emojis" => [], + "fields" => [], + "follow_requests_count" => 0, + "followers_count" => 0, + "following_count" => 1, + "header" => "https://mypleroma.com/images/banner.png", + "header_static" => "https://mypleroma.com/images/banner.png", + "id" => "9tKi3esbG7OQgZ2920", + "locked" => false, + "note" => "cofe", + "pleroma" => %{ + "allow_following_move" => true, + "background_image" => nil, + "confirmation_pending" => true, + "hide_favorites" => true, + "hide_followers" => false, + "hide_followers_count" => false, + "hide_follows" => false, + "hide_follows_count" => false, + "is_admin" => false, + "is_moderator" => false, + "skip_thread_containment" => false, + "chat_token" => + "SFMyNTY.g3QAAAACZAAEZGF0YW0AAAASOXRLaTNlc2JHN09RZ1oyOTIwZAAGc2lnbmVkbgYARNplS3EB.Mb_Iaqew2bN1I1o79B_iP7encmVCpTKC4OtHZRxdjKc", + "unread_conversation_count" => 0, + "tags" => [], + "notification_settings" => %{ + "followers" => true, + "follows" => true, + "non_followers" => true, + "non_follows" => true, + "privacy_option" => false + }, + "relationship" => %{ + "blocked_by" => false, + "blocking" => false, + "domain_blocking" => false, + "endorsed" => false, + "followed_by" => false, + "following" => false, + "id" => "9tKi3esbG7OQgZ2920", + "muting" => false, + "muting_notifications" => false, + "requested" => false, + "showing_reblogs" => true, + "subscribing" => false + }, + "settings_store" => %{ + "pleroma-fe" => %{} + } + }, + "source" => %{ "fields" => [], - "follow_requests_count" => 0, - "followers_count" => 0, - "following_count" => 1, - "header" => "https://mypleroma.com/images/banner.png", - "header_static" => "https://mypleroma.com/images/banner.png", - "id" => "9tKi3esbG7OQgZ2920", - "locked" => false, - "note" => "cofe", + "note" => "foobar", "pleroma" => %{ - "allow_following_move" => true, - "background_image" => nil, - "confirmation_pending" => true, - "hide_favorites" => true, - "hide_followers" => false, - "hide_followers_count" => false, - "hide_follows" => false, - "hide_follows_count" => false, - "is_admin" => false, - "is_moderator" => false, - "skip_thread_containment" => false, - "chat_token" => - "SFMyNTY.g3QAAAACZAAEZGF0YW0AAAASOXRLaTNlc2JHN09RZ1oyOTIwZAAGc2lnbmVkbgYARNplS3EB.Mb_Iaqew2bN1I1o79B_iP7encmVCpTKC4OtHZRxdjKc", - "unread_conversation_count" => 0, - "tags" => [], - "notification_settings" => %{ - "followers" => true, - "follows" => true, - "non_followers" => true, - "non_follows" => true, - "privacy_option" => false - }, - "relationship" => %{ - "blocked_by" => false, - "blocking" => false, - "domain_blocking" => false, - "endorsed" => false, - "followed_by" => false, - "following" => false, - "id" => "9tKi3esbG7OQgZ2920", - "muting" => false, - "muting_notifications" => false, - "requested" => false, - "showing_reblogs" => true, - "subscribing" => false - }, - "settings_store" => %{ - "pleroma-fe" => %{} - } - }, - "source" => %{ - "fields" => [], - "note" => "foobar", - "pleroma" => %{ - "actor_type" => "Person", - "discoverable" => false, - "no_rich_text" => false, - "show_role" => true - }, - "privacy" => "public", - "sensitive" => false + "actor_type" => "Person", + "discoverable" => false, + "no_rich_text" => false, + "show_role" => true }, - "statuses_count" => 0, - "url" => "https://mypleroma.com/users/foobar", - "username" => "foobar" - } + "privacy" => "public", + "sensitive" => false + }, + "statuses_count" => 0, + "url" => "https://mypleroma.com/users/foobar", + "username" => "foobar" } }) end diff --git a/lib/pleroma/web/api_spec/schemas/account_create_request.ex b/lib/pleroma/web/api_spec/schemas/account_create_request.ex index 398e2d613..49fa12159 100644 --- a/lib/pleroma/web/api_spec/schemas/account_create_request.ex +++ b/lib/pleroma/web/api_spec/schemas/account_create_request.ex @@ -23,7 +23,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest do "The email address to be used for login. Required when `account_activation_required` is enabled.", format: :email }, - password: %Schema{type: :string, description: "The password to be used for login"}, + password: %Schema{ + type: :string, + description: "The password to be used for login", + format: :password + }, agreement: %Schema{ type: :boolean, description: diff --git a/lib/pleroma/web/api_spec/schemas/account_create_response.ex b/lib/pleroma/web/api_spec/schemas/account_create_response.ex index f41a034c0..2237351a2 100644 --- a/lib/pleroma/web/api_spec/schemas/account_create_response.ex +++ b/lib/pleroma/web/api_spec/schemas/account_create_response.ex @@ -15,15 +15,13 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse do token_type: %Schema{type: :string}, access_token: %Schema{type: :string}, scope: %Schema{type: :array, items: %Schema{type: :string}}, - created_at: %Schema{type: :integer} + created_at: %Schema{type: :integer, format: :"date-time"} }, example: %{ - "JSON" => %{ - "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk", - "created_at" => 1_585_918_714, - "scope" => ["read", "write", "follow", "push"], - "token_type" => "Bearer" - } + "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk", + "created_at" => 1_585_918_714, + "scope" => ["read", "write", "follow", "push"], + "token_type" => "Bearer" } }) end diff --git a/lib/pleroma/web/api_spec/schemas/account_emoji.ex b/lib/pleroma/web/api_spec/schemas/account_emoji.ex index 403b13b15..6c1d4d95c 100644 --- a/lib/pleroma/web/api_spec/schemas/account_emoji.ex +++ b/lib/pleroma/web/api_spec/schemas/account_emoji.ex @@ -13,19 +13,17 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountEmoji do type: :object, properties: %{ shortcode: %Schema{type: :string}, - url: %Schema{type: :string}, - static_url: %Schema{type: :string}, + url: %Schema{type: :string, format: :uri}, + static_url: %Schema{type: :string, format: :uri}, visible_in_picker: %Schema{type: :boolean} }, example: %{ - "JSON" => %{ - "shortcode" => "fatyoshi", - "url" => - "https://files.mastodon.social/custom_emojis/images/000/023/920/original/e57ecb623faa0dc9.png", - "static_url" => - "https://files.mastodon.social/custom_emojis/images/000/023/920/static/e57ecb623faa0dc9.png", - "visible_in_picker" => true - } + "shortcode" => "fatyoshi", + "url" => + "https://files.mastodon.social/custom_emojis/images/000/023/920/original/e57ecb623faa0dc9.png", + "static_url" => + "https://files.mastodon.social/custom_emojis/images/000/023/920/static/e57ecb623faa0dc9.png", + "visible_in_picker" => true } }) end diff --git a/lib/pleroma/web/api_spec/schemas/account_field.ex b/lib/pleroma/web/api_spec/schemas/account_field.ex index 8906d812d..fa97073a0 100644 --- a/lib/pleroma/web/api_spec/schemas/account_field.ex +++ b/lib/pleroma/web/api_spec/schemas/account_field.ex @@ -13,16 +13,14 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountField do type: :object, properties: %{ name: %Schema{type: :string}, - value: %Schema{type: :string}, - verified_at: %Schema{type: :string, format: "date-time", nullable: true} + value: %Schema{type: :string, format: :html}, + verified_at: %Schema{type: :string, format: :"date-time", nullable: true} }, example: %{ - "JSON" => %{ - "name" => "Website", - "value" => - "https://pleroma.com", - "verified_at" => "2019-08-29T04:14:55.571+00:00" - } + "name" => "Website", + "value" => + "https://pleroma.com", + "verified_at" => "2019-08-29T04:14:55.571+00:00" } }) end diff --git a/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex b/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex index fbbdf95f5..89e483655 100644 --- a/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex +++ b/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex @@ -17,10 +17,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountAttributeField do }, required: [:name, :value], example: %{ - "JSON" => %{ - "name" => "Website", - "value" => "https://pleroma.com" - } + "name" => "Website", + "value" => "https://pleroma.com" } }) end diff --git a/lib/pleroma/web/api_spec/schemas/account_follows_request.ex b/lib/pleroma/web/api_spec/schemas/account_follows_request.ex index 4fbe615d6..19dce0cb2 100644 --- a/lib/pleroma/web/api_spec/schemas/account_follows_request.ex +++ b/lib/pleroma/web/api_spec/schemas/account_follows_request.ex @@ -11,7 +11,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountFollowsRequest do description: "POST body for muting an account", type: :object, properties: %{ - uri: %Schema{type: :string} + uri: %Schema{type: :string, format: :uri} }, required: [:uri] }) diff --git a/lib/pleroma/web/api_spec/schemas/account_relationship.ex b/lib/pleroma/web/api_spec/schemas/account_relationship.ex index 7db3b49bb..f2bd37d39 100644 --- a/lib/pleroma/web/api_spec/schemas/account_relationship.ex +++ b/lib/pleroma/web/api_spec/schemas/account_relationship.ex @@ -26,20 +26,18 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do subscribing: %Schema{type: :boolean} }, example: %{ - "JSON" => %{ - "blocked_by" => false, - "blocking" => false, - "domain_blocking" => false, - "endorsed" => false, - "followed_by" => false, - "following" => false, - "id" => "9tKi3esbG7OQgZ2920", - "muting" => false, - "muting_notifications" => false, - "requested" => false, - "showing_reblogs" => true, - "subscribing" => false - } + "blocked_by" => false, + "blocking" => false, + "domain_blocking" => false, + "endorsed" => false, + "followed_by" => false, + "following" => false, + "id" => "9tKi3esbG7OQgZ2920", + "muting" => false, + "muting_notifications" => false, + "requested" => false, + "showing_reblogs" => true, + "subscribing" => false } }) end diff --git a/lib/pleroma/web/api_spec/schemas/app_create_request.ex b/lib/pleroma/web/api_spec/schemas/app_create_request.ex index 8a83abef3..7e92205cf 100644 --- a/lib/pleroma/web/api_spec/schemas/app_create_request.ex +++ b/lib/pleroma/web/api_spec/schemas/app_create_request.ex @@ -21,7 +21,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AppCreateRequest do type: :string, description: "Space separated list of scopes. If none is provided, defaults to `read`." }, - website: %Schema{type: :string, description: "A URL to the homepage of your app"} + website: %Schema{ + type: :string, + description: "A URL to the homepage of your app", + format: :uri + } }, required: [:client_name, :redirect_uris], example: %{ diff --git a/lib/pleroma/web/api_spec/schemas/app_create_response.ex b/lib/pleroma/web/api_spec/schemas/app_create_response.ex index f290fb031..3c41d4ee5 100644 --- a/lib/pleroma/web/api_spec/schemas/app_create_response.ex +++ b/lib/pleroma/web/api_spec/schemas/app_create_response.ex @@ -16,9 +16,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AppCreateResponse do name: %Schema{type: :string}, client_id: %Schema{type: :string}, client_secret: %Schema{type: :string}, - redirect_uri: %Schema{type: :string}, + redirect_uri: %Schema{type: :string, format: :uri}, vapid_key: %Schema{type: :string}, - website: %Schema{type: :string, nullable: true} + website: %Schema{type: :string, nullable: true, format: :uri} }, example: %{ "id" => "123", diff --git a/lib/pleroma/web/api_spec/schemas/list.ex b/lib/pleroma/web/api_spec/schemas/list.ex index 30fa7db93..f85fac2b8 100644 --- a/lib/pleroma/web/api_spec/schemas/list.ex +++ b/lib/pleroma/web/api_spec/schemas/list.ex @@ -16,10 +16,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.List do title: %Schema{type: :string} }, example: %{ - "JSON" => %{ - "id" => "123", - "title" => "my list" - } + "id" => "123", + "title" => "my list" } }) end diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index 486c3a0fe..a022450e6 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -21,7 +21,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do type: :object, properties: %{ name: %Schema{type: :string}, - website: %Schema{type: :string, nullable: true} + website: %Schema{type: :string, nullable: true, format: :uri} } }, bookmarked: %Schema{type: :boolean}, @@ -29,16 +29,16 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do type: :object, nullable: true, properties: %{ - type: %Schema{type: :string}, - provider_name: %Schema{type: :string}, - provider_url: %Schema{type: :string}, - url: %Schema{type: :string}, - image: %Schema{type: :string}, + type: %Schema{type: :string, enum: ["link", "photo", "video", "rich"]}, + provider_name: %Schema{type: :string, nullable: true}, + provider_url: %Schema{type: :string, format: :uri}, + url: %Schema{type: :string, format: :uri}, + image: %Schema{type: :string, nullable: true, format: :uri}, title: %Schema{type: :string}, description: %Schema{type: :string} } }, - content: %Schema{type: :string}, + content: %Schema{type: :string, format: :html}, created_at: %Schema{type: :string, format: "date-time"}, emojis: %Schema{type: :array, items: AccountEmoji}, favourited: %Schema{type: :boolean}, @@ -53,10 +53,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do type: :object, properties: %{ id: %Schema{type: :string}, - url: %Schema{type: :string}, - remote_url: %Schema{type: :string}, - preview_url: %Schema{type: :string}, - text_url: %Schema{type: :string}, + url: %Schema{type: :string, format: :uri}, + remote_url: %Schema{type: :string, format: :uri}, + preview_url: %Schema{type: :string, format: :uri}, + text_url: %Schema{type: :string, format: :uri}, description: %Schema{type: :string}, type: %Schema{type: :string, enum: ["image", "video", "audio", "unknown"]}, pleroma: %Schema{ @@ -74,7 +74,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do id: %Schema{type: :string}, acct: %Schema{type: :string}, username: %Schema{type: :string}, - url: %Schema{type: :string} + url: %Schema{type: :string, format: :uri} } } }, @@ -120,108 +120,106 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do type: :object, properties: %{ name: %Schema{type: :string}, - url: %Schema{type: :string} + url: %Schema{type: :string, format: :uri} } } }, - uri: %Schema{type: :string}, - url: %Schema{type: :string}, + uri: %Schema{type: :string, format: :uri}, + url: %Schema{type: :string, nullable: true, format: :uri}, visibility: VisibilityScope }, example: %{ - "JSON" => %{ - "account" => %{ - "acct" => "nick6", - "avatar" => "http://localhost:4001/images/avi.png", - "avatar_static" => "http://localhost:4001/images/avi.png", - "bot" => false, - "created_at" => "2020-04-07T19:48:51.000Z", - "display_name" => "Test テスト User 6", - "emojis" => [], - "fields" => [], - "followers_count" => 1, - "following_count" => 0, - "header" => "http://localhost:4001/images/banner.png", - "header_static" => "http://localhost:4001/images/banner.png", - "id" => "9toJCsKN7SmSf3aj5c", - "locked" => false, - "note" => "Tester Number 6", - "pleroma" => %{ - "background_image" => nil, - "confirmation_pending" => false, - "hide_favorites" => true, - "hide_followers" => false, - "hide_followers_count" => false, - "hide_follows" => false, - "hide_follows_count" => false, - "is_admin" => false, - "is_moderator" => false, - "relationship" => %{ - "blocked_by" => false, - "blocking" => false, - "domain_blocking" => false, - "endorsed" => false, - "followed_by" => false, - "following" => true, - "id" => "9toJCsKN7SmSf3aj5c", - "muting" => false, - "muting_notifications" => false, - "requested" => false, - "showing_reblogs" => true, - "subscribing" => false - }, - "skip_thread_containment" => false, - "tags" => [] - }, - "source" => %{ - "fields" => [], - "note" => "Tester Number 6", - "pleroma" => %{"actor_type" => "Person", "discoverable" => false}, - "sensitive" => false - }, - "statuses_count" => 1, - "url" => "http://localhost:4001/users/nick6", - "username" => "nick6" - }, - "application" => %{"name" => "Web", "website" => nil}, - "bookmarked" => false, - "card" => nil, - "content" => "foobar", + "account" => %{ + "acct" => "nick6", + "avatar" => "http://localhost:4001/images/avi.png", + "avatar_static" => "http://localhost:4001/images/avi.png", + "bot" => false, "created_at" => "2020-04-07T19:48:51.000Z", + "display_name" => "Test テスト User 6", "emojis" => [], - "favourited" => false, - "favourites_count" => 0, - "id" => "9toJCu5YZW7O7gfvH6", - "in_reply_to_account_id" => nil, - "in_reply_to_id" => nil, - "language" => nil, - "media_attachments" => [], - "mentions" => [], - "muted" => false, - "pinned" => false, + "fields" => [], + "followers_count" => 1, + "following_count" => 0, + "header" => "http://localhost:4001/images/banner.png", + "header_static" => "http://localhost:4001/images/banner.png", + "id" => "9toJCsKN7SmSf3aj5c", + "locked" => false, + "note" => "Tester Number 6", "pleroma" => %{ - "content" => %{"text/plain" => "foobar"}, - "conversation_id" => 345_972, - "direct_conversation_id" => nil, - "emoji_reactions" => [], - "expires_at" => nil, - "in_reply_to_account_acct" => nil, - "local" => true, - "spoiler_text" => %{"text/plain" => ""}, - "thread_muted" => false + "background_image" => nil, + "confirmation_pending" => false, + "hide_favorites" => true, + "hide_followers" => false, + "hide_followers_count" => false, + "hide_follows" => false, + "hide_follows_count" => false, + "is_admin" => false, + "is_moderator" => false, + "relationship" => %{ + "blocked_by" => false, + "blocking" => false, + "domain_blocking" => false, + "endorsed" => false, + "followed_by" => false, + "following" => true, + "id" => "9toJCsKN7SmSf3aj5c", + "muting" => false, + "muting_notifications" => false, + "requested" => false, + "showing_reblogs" => true, + "subscribing" => false + }, + "skip_thread_containment" => false, + "tags" => [] }, - "poll" => nil, - "reblog" => nil, - "reblogged" => false, - "reblogs_count" => 0, - "replies_count" => 0, - "sensitive" => false, - "spoiler_text" => "", - "tags" => [], - "uri" => "http://localhost:4001/objects/0f5dad44-0e9e-4610-b377-a2631e499190", - "url" => "http://localhost:4001/notice/9toJCu5YZW7O7gfvH6", - "visibility" => "private" - } + "source" => %{ + "fields" => [], + "note" => "Tester Number 6", + "pleroma" => %{"actor_type" => "Person", "discoverable" => false}, + "sensitive" => false + }, + "statuses_count" => 1, + "url" => "http://localhost:4001/users/nick6", + "username" => "nick6" + }, + "application" => %{"name" => "Web", "website" => nil}, + "bookmarked" => false, + "card" => nil, + "content" => "foobar", + "created_at" => "2020-04-07T19:48:51.000Z", + "emojis" => [], + "favourited" => false, + "favourites_count" => 0, + "id" => "9toJCu5YZW7O7gfvH6", + "in_reply_to_account_id" => nil, + "in_reply_to_id" => nil, + "language" => nil, + "media_attachments" => [], + "mentions" => [], + "muted" => false, + "pinned" => false, + "pleroma" => %{ + "content" => %{"text/plain" => "foobar"}, + "conversation_id" => 345_972, + "direct_conversation_id" => nil, + "emoji_reactions" => [], + "expires_at" => nil, + "in_reply_to_account_acct" => nil, + "local" => true, + "spoiler_text" => %{"text/plain" => ""}, + "thread_muted" => false + }, + "poll" => nil, + "reblog" => nil, + "reblogged" => false, + "reblogs_count" => 0, + "replies_count" => 0, + "sensitive" => false, + "spoiler_text" => "", + "tags" => [], + "uri" => "http://localhost:4001/objects/0f5dad44-0e9e-4610-b377-a2631e499190", + "url" => "http://localhost:4001/notice/9toJCu5YZW7O7gfvH6", + "visibility" => "private" } }) end -- cgit v1.2.3 From 11433cd38d9761ddf3fdb94f8c39526910b975c1 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 21 Apr 2020 23:54:45 +0400 Subject: Add OpenAPI schema for FlakeID --- lib/pleroma/web/api_spec/schemas/account.ex | 22 ++++------------------ .../web/api_spec/schemas/account_relationship.ex | 3 ++- lib/pleroma/web/api_spec/schemas/flake_id.ex | 14 ++++++++++++++ lib/pleroma/web/api_spec/schemas/poll.ex | 3 ++- lib/pleroma/web/api_spec/schemas/status.ex | 3 ++- 5 files changed, 24 insertions(+), 21 deletions(-) create mode 100644 lib/pleroma/web/api_spec/schemas/flake_id.ex (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index 3634a7c76..f57015254 100644 --- a/lib/pleroma/web/api_spec/schemas/account.ex +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -6,7 +6,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji alias Pleroma.Web.ApiSpec.Schemas.AccountField + alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship alias Pleroma.Web.ApiSpec.Schemas.ActorType + alias Pleroma.Web.ApiSpec.Schemas.FlakeID alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope require OpenApiSpex @@ -29,7 +31,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do following_count: %Schema{type: :integer}, header_static: %Schema{type: :string, format: :uri}, header: %Schema{type: :string, format: :uri}, - id: %Schema{type: :string}, + id: FlakeID, locked: %Schema{type: :boolean}, note: %Schema{type: :string, format: :html}, statuses_count: %Schema{type: :integer}, @@ -62,23 +64,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do privacy_option: %Schema{type: :boolean} } }, - relationship: %Schema{ - type: :object, - properties: %{ - blocked_by: %Schema{type: :boolean}, - blocking: %Schema{type: :boolean}, - domain_blocking: %Schema{type: :boolean}, - endorsed: %Schema{type: :boolean}, - followed_by: %Schema{type: :boolean}, - following: %Schema{type: :boolean}, - id: %Schema{type: :string}, - muting: %Schema{type: :boolean}, - muting_notifications: %Schema{type: :boolean}, - requested: %Schema{type: :boolean}, - showing_reblogs: %Schema{type: :boolean}, - subscribing: %Schema{type: :boolean} - } - }, + relationship: AccountRelationship, settings_store: %Schema{ type: :object } diff --git a/lib/pleroma/web/api_spec/schemas/account_relationship.ex b/lib/pleroma/web/api_spec/schemas/account_relationship.ex index f2bd37d39..8b982669e 100644 --- a/lib/pleroma/web/api_spec/schemas/account_relationship.ex +++ b/lib/pleroma/web/api_spec/schemas/account_relationship.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.FlakeID require OpenApiSpex @@ -18,7 +19,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do endorsed: %Schema{type: :boolean}, followed_by: %Schema{type: :boolean}, following: %Schema{type: :boolean}, - id: %Schema{type: :string}, + id: FlakeID, muting: %Schema{type: :boolean}, muting_notifications: %Schema{type: :boolean}, requested: %Schema{type: :boolean}, diff --git a/lib/pleroma/web/api_spec/schemas/flake_id.ex b/lib/pleroma/web/api_spec/schemas/flake_id.ex new file mode 100644 index 000000000..b8e03b8a1 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/flake_id.ex @@ -0,0 +1,14 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.FlakeID do + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "FlakeID", + description: + "Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are sortable strings", + type: :string + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex index 2a9975f85..5fc9e889f 100644 --- a/lib/pleroma/web/api_spec/schemas/poll.ex +++ b/lib/pleroma/web/api_spec/schemas/poll.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji + alias Pleroma.Web.ApiSpec.Schemas.FlakeID require OpenApiSpex @@ -13,7 +14,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do description: "Response schema for account custom fields", type: :object, properties: %{ - id: %Schema{type: :string}, + id: FlakeID, expires_at: %Schema{type: :string, format: "date-time"}, expired: %Schema{type: :boolean}, multiple: %Schema{type: :boolean}, diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index a022450e6..bf5f04691 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Schemas.Account alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji + alias Pleroma.Web.ApiSpec.Schemas.FlakeID alias Pleroma.Web.ApiSpec.Schemas.Poll alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope @@ -43,7 +44,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do emojis: %Schema{type: :array, items: AccountEmoji}, favourited: %Schema{type: :boolean}, favourites_count: %Schema{type: :integer}, - id: %Schema{type: :string}, + id: FlakeID, in_reply_to_account_id: %Schema{type: :string, nullable: true}, in_reply_to_id: %Schema{type: :string, nullable: true}, language: %Schema{type: :string, nullable: true}, -- cgit v1.2.3 From b03aeae8b935b0a211c51cc3be5f1c15591f5a2f Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Wed, 22 Apr 2020 15:31:41 +0000 Subject: Apply suggestion to lib/pleroma/notification.ex --- lib/pleroma/notification.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index d305a43ba..aaa675253 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -362,7 +362,7 @@ defmodule Pleroma.Notification do def get_notified_from_activity(_, _local_only), do: {[], []} - @doc "Filters out AP IDs of users who domain-block and not follow activity actor" + @doc "Filters out AP IDs domain-blocking and not following the activity's actor" def exclude_domain_blocker_ap_ids(ap_ids, activity, preloaded_users \\ []) def exclude_domain_blocker_ap_ids([], _activity, _preloaded_users), do: [] -- cgit v1.2.3 From 2958a7d246f40141a88bcb7bdd6a477c4f65f0bc Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Wed, 22 Apr 2020 18:50:25 +0300 Subject: Fixed OAuth restrictions for :api routes. Made auth info dropped for :api routes if OAuth check was neither performed nor explicitly skipped. --- lib/pleroma/plugs/oauth_scopes_plug.ex | 13 +++++++-- .../mastodon_api/controllers/account_controller.ex | 4 +-- .../web/mastodon_api/controllers/app_controller.ex | 8 +++++ .../controllers/custom_emoji_controller.ex | 6 ++++ .../controllers/instance_controller.ex | 6 ++++ .../controllers/mastodon_api_controller.ex | 6 +++- .../mastodon_api/controllers/search_controller.ex | 2 ++ .../controllers/timeline_controller.ex | 17 +++++++---- .../controllers/pleroma_api_controller.ex | 6 ++++ .../pleroma_api/controllers/scrobble_controller.ex | 6 +++- lib/pleroma/web/router.ex | 34 ++++++++++------------ .../web/twitter_api/twitter_api_controller.ex | 6 ++-- lib/pleroma/web/web.ex | 14 ++++++++- 13 files changed, 93 insertions(+), 35 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/plugs/oauth_scopes_plug.ex b/lib/pleroma/plugs/oauth_scopes_plug.ex index a61582566..efc25b79f 100644 --- a/lib/pleroma/plugs/oauth_scopes_plug.ex +++ b/lib/pleroma/plugs/oauth_scopes_plug.ex @@ -28,9 +28,7 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do conn options[:fallback] == :proceed_unauthenticated -> - conn - |> assign(:user, nil) - |> assign(:token, nil) + drop_auth_info(conn) true -> missing_scopes = scopes -- matched_scopes @@ -46,6 +44,15 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do end end + @doc "Drops authentication info from connection" + def drop_auth_info(conn) do + # To simplify debugging, setting a private variable on `conn` if auth info is dropped + conn + |> put_private(:authentication_ignored, true) + |> assign(:user, nil) + |> assign(:token, nil) + end + @doc "Filters descendants of supported scopes" def filter_descendants(scopes, supported_scopes) do Enum.filter( diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 9b8cc0d0d..e501b3555 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -37,7 +37,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug( OAuthScopesPlug, %{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]} - when action in [:show, :endorsements] + when action in [:show, :followers, :following, :endorsements] ) plug( @@ -49,7 +49,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug( OAuthScopesPlug, %{scopes: ["read:accounts"]} - when action in [:endorsements, :verify_credentials, :followers, :following] + when action in [:endorsements, :verify_credentials] ) plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :update_credentials) diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex index 005c60444..408e11474 100644 --- a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.MastodonAPI.AppController do use Pleroma.Web, :controller + alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Repo alias Pleroma.Web.OAuth.App @@ -13,7 +14,14 @@ defmodule Pleroma.Web.MastodonAPI.AppController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + plug( + :skip_plug, + [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] + when action == :create + ) + plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials) + plug(OpenApiSpex.Plug.CastAndValidate) @local_mastodon_name "Mastodon-Local" diff --git a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex index 3bfebef8b..000ad743f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex @@ -7,6 +7,12 @@ defmodule Pleroma.Web.MastodonAPI.CustomEmojiController do plug(OpenApiSpex.Plug.CastAndValidate) + plug( + :skip_plug, + [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug] + when action == :index + ) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.CustomEmojiOperation def index(conn, _params) do diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex index 27b5b1a52..237f85677 100644 --- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -5,6 +5,12 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do use Pleroma.Web, :controller + plug( + :skip_plug, + [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug] + when action in [:show, :peers] + ) + @doc "GET /api/v1/instance" def show(conn, _params) do render(conn, "show.json") diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index f0492b189..e7767de4e 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -15,7 +15,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do require Logger - plug(:skip_plug, Pleroma.Plugs.OAuthScopesPlug when action in [:empty_array, :empty_object]) + plug( + :skip_plug, + [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug] + when action in [:empty_array, :empty_object] + ) action_fallback(Pleroma.Web.MastodonAPI.FallbackController) diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index b54c56967..cd49da6ad 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -21,6 +21,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do # Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search) plug(OAuthScopesPlug, %{scopes: ["read:search"], fallback: :proceed_unauthenticated}) + # Note: on private instances auth is required (EnsurePublicOrAuthenticatedPlug is not skipped) + plug(RateLimiter, [name: :search] when action in [:search, :search2, :account_search]) def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 891f924bc..040a0b9dd 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1, skip_relationships?: 1] alias Pleroma.Pagination + alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.RateLimiter alias Pleroma.User @@ -26,7 +27,13 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct]) plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :list) - plug(:skip_plug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action == :public) + plug( + OAuthScopesPlug, + %{scopes: ["read:statuses"], fallback: :proceed_unauthenticated} + when action in [:public, :hashtag] + ) + + plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:public, :hashtag]) plug(:put_view, Pleroma.Web.MastodonAPI.StatusView) @@ -93,7 +100,9 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do restrict? = Pleroma.Config.get([:restrict_unauthenticated, :timelines, cfg_key]) - if not (restrict? and is_nil(user)) do + if restrict? and is_nil(user) do + render_error(conn, :unauthorized, "authorization required for timeline view") + else activities = params |> Map.put("type", ["Create", "Announce"]) @@ -110,12 +119,10 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do as: :activity, skip_relationships: skip_relationships?(params) ) - else - render_error(conn, :unauthorized, "authorization required for timeline view") end end - def hashtag_fetching(params, user, local_only) do + defp hashtag_fetching(params, user, local_only) do tags = [params["tag"], params["any"]] |> List.flatten() diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index 7a65697e8..2c1874051 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -26,6 +26,12 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do when action in [:conversation, :conversation_statuses] ) + plug( + OAuthScopesPlug, + %{scopes: ["read:statuses"], fallback: :proceed_unauthenticated} + when action == :emoji_reactions_by + ) + plug( OAuthScopesPlug, %{scopes: ["write:statuses"]} diff --git a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex index c81e8535e..22da6c0ad 100644 --- a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex @@ -13,7 +13,11 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleController do alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.StatusView - plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :user_scrobbles) + plug( + OAuthScopesPlug, + %{scopes: ["read"], fallback: :proceed_unauthenticated} when action == :user_scrobbles + ) + plug(OAuthScopesPlug, %{scopes: ["write"]} when action != :user_scrobbles) def new_scrobble(%{assigns: %{user: user}} = conn, %{"title" => _} = params) do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 04c1c5941..db158d366 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -312,14 +312,10 @@ defmodule Pleroma.Web.Router do post("/scrobble", ScrobbleController, :new_scrobble) end - scope [] do - pipe_through(:api) - get("/accounts/:id/favourites", AccountController, :favourites) - end - scope [] do pipe_through(:authenticated_api) + get("/accounts/:id/favourites", AccountController, :favourites) post("/accounts/:id/subscribe", AccountController, :subscribe) post("/accounts/:id/unsubscribe", AccountController, :unsubscribe) end @@ -353,6 +349,8 @@ defmodule Pleroma.Web.Router do post("/accounts/:id/mute", AccountController, :mute) post("/accounts/:id/unmute", AccountController, :unmute) + get("/apps/verify_credentials", AppController, :verify_credentials) + get("/conversations", ConversationController, :index) post("/conversations/:id/read", ConversationController, :mark_as_read) @@ -431,6 +429,7 @@ defmodule Pleroma.Web.Router do get("/timelines/home", TimelineController, :home) get("/timelines/direct", TimelineController, :direct) + get("/timelines/list/:list_id", TimelineController, :list) end scope "/api/web", Pleroma.Web do @@ -442,15 +441,24 @@ defmodule Pleroma.Web.Router do scope "/api/v1", Pleroma.Web.MastodonAPI do pipe_through(:api) - post("/accounts", AccountController, :create) get("/accounts/search", SearchController, :account_search) + get("/search", SearchController, :search) + + get("/accounts/:id/statuses", AccountController, :statuses) + get("/accounts/:id/followers", AccountController, :followers) + get("/accounts/:id/following", AccountController, :following) + get("/accounts/:id", AccountController, :show) + + post("/accounts", AccountController, :create) get("/instance", InstanceController, :show) get("/instance/peers", InstanceController, :peers) post("/apps", AppController, :create) - get("/apps/verify_credentials", AppController, :verify_credentials) + get("/statuses", StatusController, :index) + get("/statuses/:id", StatusController, :show) + get("/statuses/:id/context", StatusController, :context) get("/statuses/:id/card", StatusController, :card) get("/statuses/:id/favourited_by", StatusController, :favourited_by) get("/statuses/:id/reblogged_by", StatusController, :reblogged_by) @@ -461,20 +469,8 @@ defmodule Pleroma.Web.Router do get("/timelines/public", TimelineController, :public) get("/timelines/tag/:tag", TimelineController, :hashtag) - get("/timelines/list/:list_id", TimelineController, :list) - - get("/statuses", StatusController, :index) - get("/statuses/:id", StatusController, :show) - get("/statuses/:id/context", StatusController, :context) get("/polls/:id", PollController, :show) - - get("/accounts/:id/statuses", AccountController, :statuses) - get("/accounts/:id/followers", AccountController, :followers) - get("/accounts/:id/following", AccountController, :following) - get("/accounts/:id", AccountController, :show) - - get("/search", SearchController, :search) end scope "/api/v2", Pleroma.Web.MastodonAPI do diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 55228616a..e4f182b02 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -18,7 +18,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do %{scopes: ["write:notifications"]} when action == :mark_notifications_as_read ) - plug(:skip_plug, OAuthScopesPlug when action in [:oauth_tokens, :revoke_token]) + plug(:skip_plug, OAuthScopesPlug when action in [:confirm_email, :oauth_tokens, :revoke_token]) action_fallback(:errors) @@ -47,13 +47,13 @@ defmodule Pleroma.Web.TwitterAPI.Controller do json_reply(conn, 201, "") end - def errors(conn, {:param_cast, _}) do + defp errors(conn, {:param_cast, _}) do conn |> put_status(400) |> json("Invalid parameters") end - def errors(conn, _) do + defp errors(conn, _) do conn |> put_status(500) |> json("Something went wrong") diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex index ec04c05f0..e2416fb2e 100644 --- a/lib/pleroma/web/web.ex +++ b/lib/pleroma/web/web.ex @@ -67,13 +67,25 @@ defmodule Pleroma.Web do # Executed just before actual controller action, invokes before-action hooks (callbacks) defp action(conn, params) do - with %Plug.Conn{halted: false} <- maybe_perform_public_or_authenticated_check(conn), + with %Plug.Conn{halted: false} <- maybe_drop_authentication_if_oauth_check_ignored(conn), + %Plug.Conn{halted: false} <- maybe_perform_public_or_authenticated_check(conn), %Plug.Conn{halted: false} <- maybe_perform_authenticated_check(conn), %Plug.Conn{halted: false} <- maybe_halt_on_missing_oauth_scopes_check(conn) do super(conn, params) end end + # For non-authenticated API actions, drops auth info if OAuth scopes check was ignored + # (neither performed nor explicitly skipped) + defp maybe_drop_authentication_if_oauth_check_ignored(conn) do + if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) and + not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do + OAuthScopesPlug.drop_auth_info(conn) + else + conn + end + end + # Ensures instance is public -or- user is authenticated if such check was scheduled defp maybe_perform_public_or_authenticated_check(conn) do if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) do -- cgit v1.2.3 From 1b06a27746ccbbdec77b7bc1571783a64ade4431 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 22 Apr 2020 20:20:19 +0400 Subject: Update Flake ID description --- lib/pleroma/web/api_spec/schemas/flake_id.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/schemas/flake_id.ex b/lib/pleroma/web/api_spec/schemas/flake_id.ex index b8e03b8a1..3b5f6477a 100644 --- a/lib/pleroma/web/api_spec/schemas/flake_id.ex +++ b/lib/pleroma/web/api_spec/schemas/flake_id.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.FlakeID do OpenApiSpex.schema(%{ title: "FlakeID", description: - "Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are sortable strings", + "Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are lexically sortable strings", type: :string }) end -- cgit v1.2.3 From 89f38d94c754d33736e2444859d6020b2147eaf7 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Thu, 23 Apr 2020 21:47:33 +0300 Subject: [#2409] Fixed before-action callback results persistence. --- lib/pleroma/web/web.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex index e2416fb2e..08e42a7e5 100644 --- a/lib/pleroma/web/web.ex +++ b/lib/pleroma/web/web.ex @@ -67,10 +67,10 @@ defmodule Pleroma.Web do # Executed just before actual controller action, invokes before-action hooks (callbacks) defp action(conn, params) do - with %Plug.Conn{halted: false} <- maybe_drop_authentication_if_oauth_check_ignored(conn), - %Plug.Conn{halted: false} <- maybe_perform_public_or_authenticated_check(conn), - %Plug.Conn{halted: false} <- maybe_perform_authenticated_check(conn), - %Plug.Conn{halted: false} <- maybe_halt_on_missing_oauth_scopes_check(conn) do + with %{halted: false} = conn <- maybe_drop_authentication_if_oauth_check_ignored(conn), + %{halted: false} = conn <- maybe_perform_public_or_authenticated_check(conn), + %{halted: false} = conn <- maybe_perform_authenticated_check(conn), + %{halted: false} = conn <- maybe_halt_on_missing_oauth_scopes_check(conn) do super(conn, params) end end -- cgit v1.2.3 From 00e62161f64802317d7d789e7eac42c33f0540f5 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Fri, 24 Apr 2020 16:52:38 +0300 Subject: [#2409] Tested all auth setup configs in AuthTestControllerTest. Adjusted :skip_plug definitions for some endpoints. --- lib/pleroma/tests/auth_test_controller.ex | 93 ++++++++++++++++++++++ lib/pleroma/tests/oauth_test_controller.ex | 31 -------- lib/pleroma/web/masto_fe_controller.ex | 5 +- .../mastodon_api/controllers/account_controller.ex | 13 ++- .../mastodon_api/controllers/status_controller.ex | 4 +- .../controllers/timeline_controller.ex | 4 +- lib/pleroma/web/oauth/oauth_controller.ex | 5 +- .../pleroma_api/controllers/account_controller.ex | 5 +- lib/pleroma/web/router.ex | 21 ++++- .../web/twitter_api/twitter_api_controller.ex | 8 +- 10 files changed, 137 insertions(+), 52 deletions(-) create mode 100644 lib/pleroma/tests/auth_test_controller.ex delete mode 100644 lib/pleroma/tests/oauth_test_controller.ex (limited to 'lib') diff --git a/lib/pleroma/tests/auth_test_controller.ex b/lib/pleroma/tests/auth_test_controller.ex new file mode 100644 index 000000000..fb04411d9 --- /dev/null +++ b/lib/pleroma/tests/auth_test_controller.ex @@ -0,0 +1,93 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +# A test controller reachable only in :test env. +defmodule Pleroma.Tests.AuthTestController do + @moduledoc false + + use Pleroma.Web, :controller + + alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.User + + # Serves only with proper OAuth token (:api and :authenticated_api) + # Skipping EnsurePublicOrAuthenticatedPlug has no effect in this case + # + # Suggested use case: all :authenticated_api endpoints (makes no sense for :api endpoints) + plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :do_oauth_check) + + # Via :api, keeps :user if token has requested scopes (if :user is dropped, serves if public) + # Via :authenticated_api, serves if token is present and has requested scopes + # + # Suggested use case: vast majority of :api endpoints (no sense for :authenticated_api ones) + plug( + OAuthScopesPlug, + %{scopes: ["read"], fallback: :proceed_unauthenticated} + when action == :fallback_oauth_check + ) + + # Keeps :user if present, executes regardless of token / token scopes + # Fails with no :user for :authenticated_api / no user for :api on private instance + # Note: EnsurePublicOrAuthenticatedPlug is not skipped (private instance fails on no :user) + # Note: Basic Auth processing results in :skip_plug call for OAuthScopesPlug + # + # Suggested use: suppressing OAuth checks for other auth mechanisms (like Basic Auth) + # For controller-level use, see :skip_oauth_skip_publicity_check instead + plug( + :skip_plug, + OAuthScopesPlug when action == :skip_oauth_check + ) + + # (Shouldn't be executed since the plug is skipped) + plug(OAuthScopesPlug, %{scopes: ["admin"]} when action == :skip_oauth_check) + + # Via :api, keeps :user if token has requested scopes, and continues with nil :user otherwise + # Via :authenticated_api, serves if token is present and has requested scopes + # + # Suggested use: as :fallback_oauth_check but open with nil :user for :api on private instances + plug( + :skip_plug, + EnsurePublicOrAuthenticatedPlug when action == :fallback_oauth_skip_publicity_check + ) + + plug( + OAuthScopesPlug, + %{scopes: ["read"], fallback: :proceed_unauthenticated} + when action == :fallback_oauth_skip_publicity_check + ) + + # Via :api, keeps :user if present, serves regardless of token presence / scopes / :user presence + # Via :authenticated_api, serves if :user is set (regardless of token presence and its scopes) + # + # Suggested use: making an :api endpoint always accessible (e.g. email confirmation endpoint) + plug( + :skip_plug, + [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] + when action == :skip_oauth_skip_publicity_check + ) + + # Via :authenticated_api, always fails with 403 (endpoint is insecure) + # Via :api, drops :user if present and serves if public (private instance rejects on no user) + # + # Suggested use: none; please define OAuth rules for all :api / :authenticated_api endpoints + plug(:skip_plug, [] when action == :missing_oauth_check_definition) + + def do_oauth_check(conn, _params), do: conn_state(conn) + + def fallback_oauth_check(conn, _params), do: conn_state(conn) + + def skip_oauth_check(conn, _params), do: conn_state(conn) + + def fallback_oauth_skip_publicity_check(conn, _params), do: conn_state(conn) + + def skip_oauth_skip_publicity_check(conn, _params), do: conn_state(conn) + + def missing_oauth_check_definition(conn, _params), do: conn_state(conn) + + defp conn_state(%{assigns: %{user: %User{} = user}} = conn), + do: json(conn, %{user_id: user.id}) + + defp conn_state(conn), do: json(conn, %{user_id: nil}) +end diff --git a/lib/pleroma/tests/oauth_test_controller.ex b/lib/pleroma/tests/oauth_test_controller.ex deleted file mode 100644 index 58d517f78..000000000 --- a/lib/pleroma/tests/oauth_test_controller.ex +++ /dev/null @@ -1,31 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -# A test controller reachable only in :test env. -# Serves to test OAuth scopes check skipping / enforcement. -defmodule Pleroma.Tests.OAuthTestController do - @moduledoc false - - use Pleroma.Web, :controller - - alias Pleroma.Plugs.OAuthScopesPlug - - plug(:skip_plug, OAuthScopesPlug when action == :skipped_oauth) - - plug(OAuthScopesPlug, %{scopes: ["read"]} when action != :missed_oauth) - - def skipped_oauth(conn, _params) do - noop(conn) - end - - def performed_oauth(conn, _params) do - noop(conn) - end - - def missed_oauth(conn, _params) do - noop(conn) - end - - defp noop(conn), do: json(conn, %{}) -end diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex index 9a2ec517a..d0d8bc8eb 100644 --- a/lib/pleroma/web/masto_fe_controller.ex +++ b/lib/pleroma/web/masto_fe_controller.ex @@ -5,12 +5,15 @@ defmodule Pleroma.Web.MastoFEController do use Pleroma.Web, :controller + alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :put_settings) # Note: :index action handles attempt of unauthenticated access to private instance with redirect + plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action == :index) + plug( OAuthScopesPlug, %{scopes: ["read"], fallback: :proceed_unauthenticated} @@ -19,7 +22,7 @@ defmodule Pleroma.Web.MastoFEController do plug( :skip_plug, - Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :manifest] + [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :manifest ) @doc "GET /web/*path" diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index e3465e659..f39825e08 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -14,6 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do skip_relationships?: 1 ] + alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.RateLimiter alias Pleroma.User @@ -26,18 +27,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do alias Pleroma.Web.OAuth.Token alias Pleroma.Web.TwitterAPI.TwitterAPI - plug(:skip_plug, OAuthScopesPlug when action in [:create, :identity_proofs]) + plug(:skip_plug, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :create) - plug( - :skip_plug, - Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug - when action in [:create, :show, :statuses] - ) + plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:show, :statuses]) plug( OAuthScopesPlug, %{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]} - when action in [:show, :followers, :following, :endorsements] + when action in [:show, :followers, :following] ) plug( @@ -49,7 +46,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug( OAuthScopesPlug, %{scopes: ["read:accounts"]} - when action in [:endorsements, :verify_credentials] + when action in [:verify_credentials, :endorsements, :identity_proofs] ) plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :update_credentials) diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index eade83aaf..4fa9a2120 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -24,6 +24,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.ScheduledActivityView + plug(:skip_plug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show]) + @unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []} plug( @@ -77,8 +79,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do %{scopes: ["write:bookmarks"]} when action in [:bookmark, :unbookmark] ) - plug(:skip_plug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show]) - @rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete)a plug( diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 040a0b9dd..fb6b18ed5 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -15,6 +15,8 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub + 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: # https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e @@ -33,8 +35,6 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do when action in [:public, :hashtag] ) - plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:public, :hashtag]) - plug(:put_view, Pleroma.Web.MastodonAPI.StatusView) # GET /api/v1/timelines/home diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 0121cd661..685269877 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -25,9 +25,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do plug(:fetch_session) plug(:fetch_flash) - plug(RateLimiter, [name: :authentication] when action == :create_authorization) - plug(:skip_plug, Pleroma.Plugs.OAuthScopesPlug) + plug(:skip_plug, [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]) + + plug(RateLimiter, [name: :authentication] when action == :create_authorization) action_fallback(Pleroma.Web.OAuth.FallbackController) diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex index d6ffdcbe4..237c8157e 100644 --- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2, skip_relationships?: 1] alias Ecto.Changeset + alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.RateLimiter alias Pleroma.User @@ -17,11 +18,9 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do require Pleroma.Constants - plug(:skip_plug, OAuthScopesPlug when action == :confirmation_resend) - plug( :skip_plug, - Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action == :confirmation_resend + [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :confirmation_resend ) plug( diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index db158d366..57efc3314 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -655,11 +655,28 @@ defmodule Pleroma.Web.Router do # Test-only routes needed to test action dispatching and plug chain execution if Pleroma.Config.get(:env) == :test do + @test_actions [ + :do_oauth_check, + :fallback_oauth_check, + :skip_oauth_check, + :fallback_oauth_skip_publicity_check, + :skip_oauth_skip_publicity_check, + :missing_oauth_check_definition + ] + + scope "/test/api", Pleroma.Tests do + pipe_through(:api) + + for action <- @test_actions do + get("/#{action}", AuthTestController, action) + end + end + scope "/test/authenticated_api", Pleroma.Tests do pipe_through(:authenticated_api) - for action <- [:skipped_oauth, :performed_oauth, :missed_oauth] do - get("/#{action}", OAuthTestController, action) + for action <- @test_actions do + get("/#{action}", AuthTestController, action) end end end diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index e4f182b02..c2de26b0b 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do use Pleroma.Web, :controller alias Pleroma.Notification + alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User alias Pleroma.Web.OAuth.Token @@ -18,7 +19,12 @@ defmodule Pleroma.Web.TwitterAPI.Controller do %{scopes: ["write:notifications"]} when action == :mark_notifications_as_read ) - plug(:skip_plug, OAuthScopesPlug when action in [:confirm_email, :oauth_tokens, :revoke_token]) + plug( + :skip_plug, + [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :confirm_email + ) + + plug(:skip_plug, OAuthScopesPlug when action in [:oauth_tokens, :revoke_token]) action_fallback(:errors) -- cgit v1.2.3 From b4139cc5472079a34f0256ac9991a0222844d44c Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Fri, 24 Apr 2020 22:25:27 +0300 Subject: [#2409] Made `GET /api/v1/accounts/:id/favourites` auth-optional, adjusted tests. --- lib/pleroma/web/mastodon_api/controllers/status_controller.ex | 2 +- lib/pleroma/web/pleroma_api/controllers/account_controller.ex | 5 ++++- lib/pleroma/web/router.ex | 7 ++++++- 3 files changed, 11 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 4fa9a2120..45601ff59 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -357,7 +357,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "GET /api/v1/favourites" - def favourites(%{assigns: %{user: user}} = conn, params) do + def favourites(%{assigns: %{user: %User{} = user}} = conn, params) do activities = ActivityPub.fetch_favourites( user, diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex index 237c8157e..be7477867 100644 --- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex @@ -39,7 +39,10 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do ] ) - plug(OAuthScopesPlug, %{scopes: ["read:favourites"]} when action == :favourites) + plug( + OAuthScopesPlug, + %{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites + ) plug(RateLimiter, [name: :account_confirmation_resend] when action == :confirmation_resend) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 57efc3314..becce3098 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -312,10 +312,14 @@ defmodule Pleroma.Web.Router do post("/scrobble", ScrobbleController, :new_scrobble) end + scope [] do + pipe_through(:api) + get("/accounts/:id/favourites", AccountController, :favourites) + end + scope [] do pipe_through(:authenticated_api) - get("/accounts/:id/favourites", AccountController, :favourites) post("/accounts/:id/subscribe", AccountController, :subscribe) post("/accounts/:id/unsubscribe", AccountController, :unsubscribe) end @@ -404,6 +408,7 @@ defmodule Pleroma.Web.Router do put("/scheduled_statuses/:id", ScheduledActivityController, :update) delete("/scheduled_statuses/:id", ScheduledActivityController, :delete) + # Unlike `GET /api/v1/accounts/:id/favourites`, demands authentication get("/favourites", StatusController, :favourites) get("/bookmarks", StatusController, :bookmarks) -- cgit v1.2.3 From 3635a9c9c25db16be292c5f56c27ab5d5f5affb5 Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 27 Apr 2020 14:28:08 +0200 Subject: InstanceController: Add extensions to `/api/v1/instance` --- .../web/mastodon_api/views/instance_view.ex | 58 +++++++++++++++++++++- lib/pleroma/web/nodeinfo/nodeinfo_controller.ex | 47 ++---------------- 2 files changed, 60 insertions(+), 45 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 67214dbea..a329ffc28 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -5,10 +5,13 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do use Pleroma.Web, :view + alias Pleroma.Config + alias Pleroma.Web.ActivityPub.MRF + @mastodon_api_level "2.7.2" def render("show.json", _) do - instance = Pleroma.Config.get(:instance) + instance = Config.get(:instance) %{ uri: Pleroma.Web.base_url(), @@ -29,7 +32,58 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do upload_limit: Keyword.get(instance, :upload_limit), avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit), background_upload_limit: Keyword.get(instance, :background_upload_limit), - banner_upload_limit: Keyword.get(instance, :banner_upload_limit) + banner_upload_limit: Keyword.get(instance, :banner_upload_limit), + pleroma: %{ + metadata: %{ + features: features(), + federation: federation() + }, + vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) + } } end + + def features do + [ + "pleroma_api", + "mastodon_api", + "mastodon_api_streaming", + "polls", + "pleroma_explicit_addressing", + "shareable_emoji_packs", + "multifetch", + "pleroma:api/v1/notifications:include_types_filter", + if Config.get([:media_proxy, :enabled]) do + "media_proxy" + end, + if Config.get([:gopher, :enabled]) do + "gopher" + end, + if Config.get([:chat, :enabled]) do + "chat" + end, + if Config.get([:instance, :allow_relay]) do + "relay" + end, + if Config.get([:instance, :safe_dm_mentions]) do + "safe_dm_mentions" + end, + "pleroma_emoji_reactions" + ] + |> Enum.filter(& &1) + end + + def federation do + quarantined = Config.get([:instance, :quarantined_instances], []) + + if Config.get([:instance, :mrf_transparency]) do + {:ok, data} = MRF.describe() + + data + |> Map.merge(%{quarantined_instances: quarantined}) + else + %{} + end + |> Map.put(:enabled, Config.get([:instance, :federating])) + end end diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index f9a5ddcc0..721b599d4 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -9,8 +9,8 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do alias Pleroma.Stats alias Pleroma.User alias Pleroma.Web - alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.Federator.Publisher + alias Pleroma.Web.MastodonAPI.InstanceView def schemas(conn, _params) do response = %{ @@ -34,51 +34,12 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do def raw_nodeinfo do stats = Stats.get_stats() - quarantined = Config.get([:instance, :quarantined_instances], []) - staff_accounts = User.all_superusers() |> Enum.map(fn u -> u.ap_id end) - federation_response = - if Config.get([:instance, :mrf_transparency]) do - {:ok, data} = MRF.describe() - - data - |> Map.merge(%{quarantined_instances: quarantined}) - else - %{} - end - |> Map.put(:enabled, Config.get([:instance, :federating])) - - features = - [ - "pleroma_api", - "mastodon_api", - "mastodon_api_streaming", - "polls", - "pleroma_explicit_addressing", - "shareable_emoji_packs", - "multifetch", - "pleroma:api/v1/notifications:include_types_filter", - if Config.get([:media_proxy, :enabled]) do - "media_proxy" - end, - if Config.get([:gopher, :enabled]) do - "gopher" - end, - if Config.get([:chat, :enabled]) do - "chat" - end, - if Config.get([:instance, :allow_relay]) do - "relay" - end, - if Config.get([:instance, :safe_dm_mentions]) do - "safe_dm_mentions" - end, - "pleroma_emoji_reactions" - ] - |> Enum.filter(& &1) + features = InstanceView.features() + federation = InstanceView.federation() %{ version: "2.0", @@ -106,7 +67,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do enabled: false }, staffAccounts: staff_accounts, - federation: federation_response, + federation: federation, pollLimits: Config.get([:instance, :poll_limits]), postFormats: Config.get([:instance, :allowed_post_formats]), uploadLimits: %{ -- cgit v1.2.3 From 2efc00b3cf5413ae7f8e8411ee1372343ee2618a Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 27 Apr 2020 20:46:52 +0400 Subject: Use `json_response_and_validate_schema/2` in tests to validate OpenAPI schema --- .../web/api_spec/operations/account_operation.ex | 27 ++++++++++++++++------ lib/pleroma/web/api_spec/schemas/account.ex | 2 +- lib/pleroma/web/controller_helper.ex | 5 ++-- .../mastodon_api/controllers/account_controller.ex | 4 ++-- 4 files changed, 26 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index fcf030037..bf8d21059 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do alias OpenApiSpex.Reference alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Schemas.Account + alias Pleroma.Web.ApiSpec.Schemas.ApiError alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse alias Pleroma.Web.ApiSpec.Schemas.AccountFollowsRequest @@ -38,7 +39,10 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do operationId: "AccountController.create", requestBody: request_body("Parameters", AccountCreateRequest, required: true), responses: %{ - 200 => Operation.response("Account", "application/json", AccountCreateResponse) + 200 => Operation.response("Account", "application/json", AccountCreateResponse), + 400 => Operation.response("Error", "application/json", ApiError), + 403 => Operation.response("Error", "application/json", ApiError), + 429 => Operation.response("Error", "application/json", ApiError) } } end @@ -65,7 +69,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do security: [%{"oAuth" => ["write:accounts"]}], requestBody: request_body("Parameters", AccountUpdateCredentialsRequest, required: true), responses: %{ - 200 => Operation.response("Account", "application/json", Account) + 200 => Operation.response("Account", "application/json", Account), + 403 => Operation.response("Error", "application/json", ApiError) } } end @@ -102,7 +107,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do description: "View information about a profile.", parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}], responses: %{ - 200 => Operation.response("Account", "application/json", Account) + 200 => Operation.response("Account", "application/json", Account), + 404 => Operation.response("Error", "application/json", ApiError) } } end @@ -140,7 +146,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do ) ] ++ pagination_params(), responses: %{ - 200 => Operation.response("Statuses", "application/json", StatusesResponse) + 200 => Operation.response("Statuses", "application/json", StatusesResponse), + 404 => Operation.response("Error", "application/json", ApiError) } } end @@ -204,7 +211,9 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do ) ], responses: %{ - 200 => Operation.response("Relationship", "application/json", AccountRelationship) + 200 => Operation.response("Relationship", "application/json", AccountRelationship), + 400 => Operation.response("Error", "application/json", ApiError), + 404 => Operation.response("Error", "application/json", ApiError) } } end @@ -218,7 +227,9 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do description: "Unfollow the given account", parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}], responses: %{ - 200 => Operation.response("Relationship", "application/json", AccountRelationship) + 200 => Operation.response("Relationship", "application/json", AccountRelationship), + 400 => Operation.response("Error", "application/json", ApiError), + 404 => Operation.response("Error", "application/json", ApiError) } } end @@ -298,7 +309,9 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do security: [%{"oAuth" => ["follow", "write:follows"]}], requestBody: request_body("Parameters", AccountFollowsRequest, required: true), responses: %{ - 200 => Operation.response("Account", "application/json", AccountRelationship) + 200 => Operation.response("Account", "application/json", AccountRelationship), + 400 => Operation.response("Error", "application/json", ApiError), + 404 => Operation.response("Error", "application/json", ApiError) } } end diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index f57015254..d128feb30 100644 --- a/lib/pleroma/web/api_spec/schemas/account.ex +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -41,7 +41,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do type: :object, properties: %{ allow_following_move: %Schema{type: :boolean}, - background_image: %Schema{type: :boolean, nullable: true}, + background_image: %Schema{type: :string, nullable: true}, chat_token: %Schema{type: :string}, confirmation_pending: %Schema{type: :boolean}, hide_favorites: %Schema{type: :boolean}, diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index 4780081b2..eb97ae975 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -82,8 +82,9 @@ defmodule Pleroma.Web.ControllerHelper do end end - def assign_account_by_id(%{params: %{"id" => id}} = conn, _) do - case Pleroma.User.get_cached_by_id(id) do + def assign_account_by_id(conn, _) do + # TODO: use `conn.params[:id]` only after moving to OpenAPI + case Pleroma.User.get_cached_by_id(conn.params[:id] || conn.params["id"]) do %Pleroma.User{} = account -> assign(conn, :account, account) nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 93df79645..b1513001b 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -26,6 +26,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do alias Pleroma.Web.OAuth.Token alias Pleroma.Web.TwitterAPI.TwitterAPI + plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) + plug(:skip_plug, OAuthScopesPlug when action == :identity_proofs) plug( @@ -83,8 +85,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug(RateLimiter, [name: :app_account_creation] when action == :create) plug(:assign_account_by_id when action in @needs_account) - plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) - action_fallback(Pleroma.Web.MastodonAPI.FallbackController) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AccountOperation -- cgit v1.2.3 From dda65f7799e9dfa2e7b87389848eeee10993a858 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 27 Apr 2020 22:55:05 +0400 Subject: Move single used schemas to operation schema --- .../web/api_spec/operations/account_operation.ex | 376 +++++++++++++++++++-- .../api_spec/operations/custom_emoji_operation.ex | 35 +- lib/pleroma/web/api_spec/schemas/account.ex | 4 +- .../web/api_spec/schemas/account_create_request.ex | 60 ---- .../api_spec/schemas/account_create_response.ex | 27 -- lib/pleroma/web/api_spec/schemas/account_emoji.ex | 29 -- .../api_spec/schemas/account_field_attribute.ex | 24 -- .../api_spec/schemas/account_follows_request.ex | 18 - .../web/api_spec/schemas/account_mute_request.ex | 24 -- .../schemas/account_relationships_response.ex | 58 ---- .../schemas/account_update_credentials_request.ex | 125 ------- .../web/api_spec/schemas/accounts_response.ex | 13 - lib/pleroma/web/api_spec/schemas/api_error.ex | 19 ++ lib/pleroma/web/api_spec/schemas/custom_emoji.ex | 30 -- lib/pleroma/web/api_spec/schemas/emoji.ex | 29 ++ lib/pleroma/web/api_spec/schemas/list.ex | 23 -- lib/pleroma/web/api_spec/schemas/lists_response.ex | 16 - lib/pleroma/web/api_spec/schemas/poll.ex | 4 +- lib/pleroma/web/api_spec/schemas/status.ex | 4 +- .../web/api_spec/schemas/statuses_response.ex | 13 - .../mastodon_api/controllers/account_controller.ex | 11 +- 21 files changed, 442 insertions(+), 500 deletions(-) delete mode 100644 lib/pleroma/web/api_spec/schemas/account_create_request.ex delete mode 100644 lib/pleroma/web/api_spec/schemas/account_create_response.ex delete mode 100644 lib/pleroma/web/api_spec/schemas/account_emoji.ex delete mode 100644 lib/pleroma/web/api_spec/schemas/account_field_attribute.ex delete mode 100644 lib/pleroma/web/api_spec/schemas/account_follows_request.ex delete mode 100644 lib/pleroma/web/api_spec/schemas/account_mute_request.ex delete mode 100644 lib/pleroma/web/api_spec/schemas/account_relationships_response.ex delete mode 100644 lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex delete mode 100644 lib/pleroma/web/api_spec/schemas/accounts_response.ex create mode 100644 lib/pleroma/web/api_spec/schemas/api_error.ex delete mode 100644 lib/pleroma/web/api_spec/schemas/custom_emoji.ex create mode 100644 lib/pleroma/web/api_spec/schemas/emoji.ex delete mode 100644 lib/pleroma/web/api_spec/schemas/list.ex delete mode 100644 lib/pleroma/web/api_spec/schemas/lists_response.ex delete mode 100644 lib/pleroma/web/api_spec/schemas/statuses_response.ex (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index bf8d21059..2efe6e901 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -7,18 +7,11 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do alias OpenApiSpex.Reference alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Schemas.Account - alias Pleroma.Web.ApiSpec.Schemas.ApiError - alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest - alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse - alias Pleroma.Web.ApiSpec.Schemas.AccountFollowsRequest - alias Pleroma.Web.ApiSpec.Schemas.AccountMuteRequest alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship - alias Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse - alias Pleroma.Web.ApiSpec.Schemas.AccountsResponse - alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest + alias Pleroma.Web.ApiSpec.Schemas.ActorType + alias Pleroma.Web.ApiSpec.Schemas.ApiError alias Pleroma.Web.ApiSpec.Schemas.BooleanLike - alias Pleroma.Web.ApiSpec.Schemas.ListsResponse - alias Pleroma.Web.ApiSpec.Schemas.StatusesResponse + alias Pleroma.Web.ApiSpec.Schemas.Status alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope import Pleroma.Web.ApiSpec.Helpers @@ -37,9 +30,9 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do description: "Creates a user and account records. Returns an account access token for the app that initiated the request. The app should save this token for later, and should wait for the user to confirm their account by clicking a link in their email inbox.", operationId: "AccountController.create", - requestBody: request_body("Parameters", AccountCreateRequest, required: true), + requestBody: request_body("Parameters", create_request(), required: true), responses: %{ - 200 => Operation.response("Account", "application/json", AccountCreateResponse), + 200 => Operation.response("Account", "application/json", create_response()), 400 => Operation.response("Error", "application/json", ApiError), 403 => Operation.response("Error", "application/json", ApiError), 429 => Operation.response("Error", "application/json", ApiError) @@ -67,7 +60,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do description: "Update the user's display and preferences.", operationId: "AccountController.update_credentials", security: [%{"oAuth" => ["write:accounts"]}], - requestBody: request_body("Parameters", AccountUpdateCredentialsRequest, required: true), + requestBody: request_body("Parameters", update_creadentials_request(), required: true), responses: %{ 200 => Operation.response("Account", "application/json", Account), 403 => Operation.response("Error", "application/json", ApiError) @@ -94,7 +87,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do ) ], responses: %{ - 200 => Operation.response("Account", "application/json", AccountRelationshipsResponse) + 200 => Operation.response("Account", "application/json", array_of_relationships()) } } end @@ -146,7 +139,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do ) ] ++ pagination_params(), responses: %{ - 200 => Operation.response("Statuses", "application/json", StatusesResponse), + 200 => Operation.response("Statuses", "application/json", array_of_statuses()), 404 => Operation.response("Error", "application/json", ApiError) } } @@ -163,7 +156,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}] ++ pagination_params(), responses: %{ - 200 => Operation.response("Accounts", "application/json", AccountsResponse) + 200 => Operation.response("Accounts", "application/json", array_of_accounts()) } } end @@ -178,7 +171,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do "Accounts which the given account is following, if network is not hidden by the account owner.", parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}] ++ pagination_params(), - responses: %{200 => Operation.response("Accounts", "application/json", AccountsResponse)} + responses: %{200 => Operation.response("Accounts", "application/json", array_of_accounts())} } end @@ -190,7 +183,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do security: [%{"oAuth" => ["read:lists"]}], description: "User lists that you have added this account to.", parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}], - responses: %{200 => Operation.response("Lists", "application/json", ListsResponse)} + responses: %{200 => Operation.response("Lists", "application/json", array_of_lists())} } end @@ -240,7 +233,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do summary: "Mute", operationId: "AccountController.mute", security: [%{"oAuth" => ["follow", "write:mutes"]}], - requestBody: request_body("Parameters", AccountMuteRequest), + requestBody: request_body("Parameters", mute_request()), description: "Mute the given account. Clients should filter statuses and notifications from this account, if received (e.g. due to a boost in the Home timeline).", parameters: [ @@ -307,7 +300,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do summary: "Follows", operationId: "AccountController.follows", security: [%{"oAuth" => ["follow", "write:follows"]}], - requestBody: request_body("Parameters", AccountFollowsRequest, required: true), + requestBody: request_body("Parameters", follows_request(), required: true), responses: %{ 200 => Operation.response("Account", "application/json", AccountRelationship), 400 => Operation.response("Error", "application/json", ApiError), @@ -324,7 +317,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do description: "Accounts the user has muted.", security: [%{"oAuth" => ["follow", "read:mutes"]}], responses: %{ - 200 => Operation.response("Accounts", "application/json", AccountsResponse) + 200 => Operation.response("Accounts", "application/json", array_of_accounts()) } } end @@ -337,7 +330,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do description: "View your blocks. See also accounts/:id/{block,unblock}", security: [%{"oAuth" => ["read:blocks"]}], responses: %{ - 200 => Operation.response("Accounts", "application/json", AccountsResponse) + 200 => Operation.response("Accounts", "application/json", array_of_accounts()) } } end @@ -366,4 +359,343 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do } } end + + defp create_request do + %Schema{ + title: "AccountCreateRequest", + description: "POST body for creating an account", + type: :object, + properties: %{ + reason: %Schema{ + type: :string, + description: + "Text that will be reviewed by moderators if registrations require manual approval" + }, + username: %Schema{type: :string, description: "The desired username for the account"}, + email: %Schema{ + type: :string, + description: + "The email address to be used for login. Required when `account_activation_required` is enabled.", + format: :email + }, + password: %Schema{ + type: :string, + description: "The password to be used for login", + format: :password + }, + agreement: %Schema{ + type: :boolean, + description: + "Whether the user agrees to the local rules, terms, and policies. These should be presented to the user in order to allow them to consent before setting this parameter to TRUE." + }, + locale: %Schema{ + type: :string, + description: "The language of the confirmation email that will be sent" + }, + # Pleroma-specific properties: + fullname: %Schema{type: :string, description: "Full name"}, + bio: %Schema{type: :string, description: "Bio", default: ""}, + captcha_solution: %Schema{ + type: :string, + description: "Provider-specific captcha solution" + }, + captcha_token: %Schema{type: :string, description: "Provider-specific captcha token"}, + captcha_answer_data: %Schema{type: :string, description: "Provider-specific captcha data"}, + token: %Schema{ + type: :string, + description: "Invite token required when the registrations aren't public" + } + }, + required: [:username, :password, :agreement], + example: %{ + "username" => "cofe", + "email" => "cofe@example.com", + "password" => "secret", + "agreement" => "true", + "bio" => "☕️" + } + } + end + + defp create_response do + %Schema{ + title: "AccountCreateResponse", + description: "Response schema for an account", + type: :object, + properties: %{ + token_type: %Schema{type: :string}, + access_token: %Schema{type: :string}, + scope: %Schema{type: :array, items: %Schema{type: :string}}, + created_at: %Schema{type: :integer, format: :"date-time"} + }, + example: %{ + "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk", + "created_at" => 1_585_918_714, + "scope" => ["read", "write", "follow", "push"], + "token_type" => "Bearer" + } + } + end + + defp update_creadentials_request do + %Schema{ + title: "AccountUpdateCredentialsRequest", + description: "POST body for creating an account", + type: :object, + properties: %{ + bot: %Schema{ + type: :boolean, + description: "Whether the account has a bot flag." + }, + display_name: %Schema{ + type: :string, + description: "The display name to use for the profile." + }, + note: %Schema{type: :string, description: "The account bio."}, + avatar: %Schema{ + type: :string, + description: "Avatar image encoded using multipart/form-data", + format: :binary + }, + header: %Schema{ + type: :string, + description: "Header image encoded using multipart/form-data", + format: :binary + }, + locked: %Schema{ + type: :boolean, + description: "Whether manual approval of follow requests is required." + }, + fields_attributes: %Schema{ + oneOf: [ + %Schema{type: :array, items: attribute_field()}, + %Schema{type: :object, additionalProperties: %Schema{type: attribute_field()}} + ] + }, + # NOTE: `source` field is not supported + # + # source: %Schema{ + # type: :object, + # properties: %{ + # privacy: %Schema{type: :string}, + # sensitive: %Schema{type: :boolean}, + # language: %Schema{type: :string} + # } + # }, + + # Pleroma-specific fields + no_rich_text: %Schema{ + type: :boolean, + description: "html tags are stripped from all statuses requested from the API" + }, + hide_followers: %Schema{type: :boolean, description: "user's followers will be hidden"}, + hide_follows: %Schema{type: :boolean, description: "user's follows will be hidden"}, + hide_followers_count: %Schema{ + type: :boolean, + description: "user's follower count will be hidden" + }, + hide_follows_count: %Schema{ + type: :boolean, + description: "user's follow count will be hidden" + }, + hide_favorites: %Schema{ + type: :boolean, + description: "user's favorites timeline will be hidden" + }, + show_role: %Schema{ + type: :boolean, + description: "user's role (e.g admin, moderator) will be exposed to anyone in the + API" + }, + default_scope: VisibilityScope, + pleroma_settings_store: %Schema{ + type: :object, + description: "Opaque user settings to be saved on the backend." + }, + skip_thread_containment: %Schema{ + type: :boolean, + description: "Skip filtering out broken threads" + }, + allow_following_move: %Schema{ + type: :boolean, + description: "Allows automatically follow moved following accounts" + }, + pleroma_background_image: %Schema{ + type: :string, + description: "Sets the background image of the user.", + format: :binary + }, + discoverable: %Schema{ + type: :boolean, + description: + "Discovery of this account in search results and other services is allowed." + }, + actor_type: ActorType + }, + example: %{ + bot: false, + display_name: "cofe", + note: "foobar", + fields_attributes: [%{name: "foo", value: "bar"}], + no_rich_text: false, + hide_followers: true, + hide_follows: false, + hide_followers_count: false, + hide_follows_count: false, + hide_favorites: false, + show_role: false, + default_scope: "private", + pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}}, + skip_thread_containment: false, + allow_following_move: false, + discoverable: false, + actor_type: "Person" + } + } + end + + defp array_of_accounts do + %Schema{ + title: "ArrayOfAccounts", + type: :array, + items: Account + } + end + + defp array_of_relationships do + %Schema{ + title: "ArrayOfRelationships", + description: "Response schema for account relationships", + type: :array, + items: AccountRelationship, + example: [ + %{ + "id" => "1", + "following" => true, + "showing_reblogs" => true, + "followed_by" => true, + "blocking" => false, + "blocked_by" => true, + "muting" => false, + "muting_notifications" => false, + "requested" => false, + "domain_blocking" => false, + "subscribing" => false, + "endorsed" => true + }, + %{ + "id" => "2", + "following" => true, + "showing_reblogs" => true, + "followed_by" => true, + "blocking" => false, + "blocked_by" => true, + "muting" => true, + "muting_notifications" => false, + "requested" => true, + "domain_blocking" => false, + "subscribing" => false, + "endorsed" => false + }, + %{ + "id" => "3", + "following" => true, + "showing_reblogs" => true, + "followed_by" => true, + "blocking" => true, + "blocked_by" => false, + "muting" => true, + "muting_notifications" => false, + "requested" => false, + "domain_blocking" => true, + "subscribing" => true, + "endorsed" => false + } + ] + } + end + + defp follows_request do + %Schema{ + title: "AccountFollowsRequest", + description: "POST body for muting an account", + type: :object, + properties: %{ + uri: %Schema{type: :string, format: :uri} + }, + required: [:uri] + } + end + + defp mute_request do + %Schema{ + title: "AccountMuteRequest", + description: "POST body for muting an account", + type: :object, + properties: %{ + notifications: %Schema{ + type: :boolean, + description: "Mute notifications in addition to statuses? Defaults to true.", + default: true + } + }, + example: %{ + "notifications" => true + } + } + end + + defp list do + %Schema{ + title: "List", + description: "Response schema for a list", + type: :object, + properties: %{ + id: %Schema{type: :string}, + title: %Schema{type: :string} + }, + example: %{ + "id" => "123", + "title" => "my list" + } + } + end + + defp array_of_lists do + %Schema{ + title: "ArrayOfLists", + description: "Response schema for lists", + type: :array, + items: list(), + example: [ + %{"id" => "123", "title" => "my list"}, + %{"id" => "1337", "title" => "anotehr list"} + ] + } + end + + defp array_of_statuses do + %Schema{ + title: "ArrayOfStatuses", + type: :array, + items: Status + } + end + + defp attribute_field do + %Schema{ + title: "AccountAttributeField", + description: "Request schema for account custom fields", + type: :object, + properties: %{ + name: %Schema{type: :string}, + value: %Schema{type: :string} + }, + required: [:name, :value], + example: %{ + "name" => "Website", + "value" => "https://pleroma.com" + } + } + end end diff --git a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex index a117fe460..2f812ac77 100644 --- a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex +++ b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex @@ -5,7 +5,7 @@ defmodule Pleroma.Web.ApiSpec.CustomEmojiOperation do alias OpenApiSpex.Operation alias OpenApiSpex.Schema - alias Pleroma.Web.ApiSpec.Schemas.CustomEmoji + alias Pleroma.Web.ApiSpec.Schemas.Emoji def open_api_operation(action) do operation = String.to_existing_atom("#{action}_operation") @@ -19,17 +19,17 @@ defmodule Pleroma.Web.ApiSpec.CustomEmojiOperation do description: "Returns custom emojis that are available on the server.", operationId: "CustomEmojiController.index", responses: %{ - 200 => Operation.response("Custom Emojis", "application/json", custom_emojis_resposnse()) + 200 => Operation.response("Custom Emojis", "application/json", resposnse()) } } end - defp custom_emojis_resposnse do + defp resposnse do %Schema{ title: "CustomEmojisResponse", description: "Response schema for custom emojis", type: :array, - items: CustomEmoji, + items: custom_emoji(), example: [ %{ "category" => "Fun", @@ -58,4 +58,31 @@ defmodule Pleroma.Web.ApiSpec.CustomEmojiOperation do ] } end + + defp custom_emoji do + %Schema{ + title: "CustomEmoji", + description: "Schema for a CustomEmoji", + allOf: [ + Emoji, + %Schema{ + type: :object, + properties: %{ + category: %Schema{type: :string}, + tags: %Schema{type: :array} + } + } + ], + example: %{ + "category" => "Fun", + "shortcode" => "aaaa", + "url" => + "https://files.mastodon.social/custom_emojis/images/000/007/118/original/aaaa.png", + "static_url" => + "https://files.mastodon.social/custom_emojis/images/000/007/118/static/aaaa.png", + "visible_in_picker" => true, + "tags" => ["Gif", "Fun"] + } + } + end end diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index d128feb30..d54e2158d 100644 --- a/lib/pleroma/web/api_spec/schemas/account.ex +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -4,10 +4,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do alias OpenApiSpex.Schema - alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji alias Pleroma.Web.ApiSpec.Schemas.AccountField alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship alias Pleroma.Web.ApiSpec.Schemas.ActorType + alias Pleroma.Web.ApiSpec.Schemas.Emoji alias Pleroma.Web.ApiSpec.Schemas.FlakeID alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope @@ -24,7 +24,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do bot: %Schema{type: :boolean}, created_at: %Schema{type: :string, format: "date-time"}, display_name: %Schema{type: :string}, - emojis: %Schema{type: :array, items: AccountEmoji}, + emojis: %Schema{type: :array, items: Emoji}, fields: %Schema{type: :array, items: AccountField}, follow_requests_count: %Schema{type: :integer}, followers_count: %Schema{type: :integer}, diff --git a/lib/pleroma/web/api_spec/schemas/account_create_request.ex b/lib/pleroma/web/api_spec/schemas/account_create_request.ex deleted file mode 100644 index 49fa12159..000000000 --- a/lib/pleroma/web/api_spec/schemas/account_create_request.ex +++ /dev/null @@ -1,60 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest do - alias OpenApiSpex.Schema - require OpenApiSpex - - OpenApiSpex.schema(%{ - title: "AccountCreateRequest", - description: "POST body for creating an account", - type: :object, - properties: %{ - reason: %Schema{ - type: :string, - description: - "Text that will be reviewed by moderators if registrations require manual approval" - }, - username: %Schema{type: :string, description: "The desired username for the account"}, - email: %Schema{ - type: :string, - description: - "The email address to be used for login. Required when `account_activation_required` is enabled.", - format: :email - }, - password: %Schema{ - type: :string, - description: "The password to be used for login", - format: :password - }, - agreement: %Schema{ - type: :boolean, - description: - "Whether the user agrees to the local rules, terms, and policies. These should be presented to the user in order to allow them to consent before setting this parameter to TRUE." - }, - locale: %Schema{ - type: :string, - description: "The language of the confirmation email that will be sent" - }, - # Pleroma-specific properties: - fullname: %Schema{type: :string, description: "Full name"}, - bio: %Schema{type: :string, description: "Bio", default: ""}, - captcha_solution: %Schema{type: :string, description: "Provider-specific captcha solution"}, - captcha_token: %Schema{type: :string, description: "Provider-specific captcha token"}, - captcha_answer_data: %Schema{type: :string, description: "Provider-specific captcha data"}, - token: %Schema{ - type: :string, - description: "Invite token required when the registrations aren't public" - } - }, - required: [:username, :password, :agreement], - example: %{ - "username" => "cofe", - "email" => "cofe@example.com", - "password" => "secret", - "agreement" => "true", - "bio" => "☕️" - } - }) -end diff --git a/lib/pleroma/web/api_spec/schemas/account_create_response.ex b/lib/pleroma/web/api_spec/schemas/account_create_response.ex deleted file mode 100644 index 2237351a2..000000000 --- a/lib/pleroma/web/api_spec/schemas/account_create_response.ex +++ /dev/null @@ -1,27 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse do - alias OpenApiSpex.Schema - - require OpenApiSpex - - OpenApiSpex.schema(%{ - title: "AccountCreateResponse", - description: "Response schema for an account", - type: :object, - properties: %{ - token_type: %Schema{type: :string}, - access_token: %Schema{type: :string}, - scope: %Schema{type: :array, items: %Schema{type: :string}}, - created_at: %Schema{type: :integer, format: :"date-time"} - }, - example: %{ - "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk", - "created_at" => 1_585_918_714, - "scope" => ["read", "write", "follow", "push"], - "token_type" => "Bearer" - } - }) -end diff --git a/lib/pleroma/web/api_spec/schemas/account_emoji.ex b/lib/pleroma/web/api_spec/schemas/account_emoji.ex deleted file mode 100644 index 6c1d4d95c..000000000 --- a/lib/pleroma/web/api_spec/schemas/account_emoji.ex +++ /dev/null @@ -1,29 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.AccountEmoji do - alias OpenApiSpex.Schema - - require OpenApiSpex - - OpenApiSpex.schema(%{ - title: "AccountEmoji", - description: "Response schema for account custom fields", - type: :object, - properties: %{ - shortcode: %Schema{type: :string}, - url: %Schema{type: :string, format: :uri}, - static_url: %Schema{type: :string, format: :uri}, - visible_in_picker: %Schema{type: :boolean} - }, - example: %{ - "shortcode" => "fatyoshi", - "url" => - "https://files.mastodon.social/custom_emojis/images/000/023/920/original/e57ecb623faa0dc9.png", - "static_url" => - "https://files.mastodon.social/custom_emojis/images/000/023/920/static/e57ecb623faa0dc9.png", - "visible_in_picker" => true - } - }) -end diff --git a/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex b/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex deleted file mode 100644 index 89e483655..000000000 --- a/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex +++ /dev/null @@ -1,24 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.AccountAttributeField do - alias OpenApiSpex.Schema - - require OpenApiSpex - - OpenApiSpex.schema(%{ - title: "AccountAttributeField", - description: "Request schema for account custom fields", - type: :object, - properties: %{ - name: %Schema{type: :string}, - value: %Schema{type: :string} - }, - required: [:name, :value], - example: %{ - "name" => "Website", - "value" => "https://pleroma.com" - } - }) -end diff --git a/lib/pleroma/web/api_spec/schemas/account_follows_request.ex b/lib/pleroma/web/api_spec/schemas/account_follows_request.ex deleted file mode 100644 index 19dce0cb2..000000000 --- a/lib/pleroma/web/api_spec/schemas/account_follows_request.ex +++ /dev/null @@ -1,18 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.AccountFollowsRequest do - alias OpenApiSpex.Schema - require OpenApiSpex - - OpenApiSpex.schema(%{ - title: "AccountFollowsRequest", - description: "POST body for muting an account", - type: :object, - properties: %{ - uri: %Schema{type: :string, format: :uri} - }, - required: [:uri] - }) -end diff --git a/lib/pleroma/web/api_spec/schemas/account_mute_request.ex b/lib/pleroma/web/api_spec/schemas/account_mute_request.ex deleted file mode 100644 index a61f6d04c..000000000 --- a/lib/pleroma/web/api_spec/schemas/account_mute_request.ex +++ /dev/null @@ -1,24 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.AccountMuteRequest do - alias OpenApiSpex.Schema - require OpenApiSpex - - OpenApiSpex.schema(%{ - title: "AccountMuteRequest", - description: "POST body for muting an account", - type: :object, - properties: %{ - notifications: %Schema{ - type: :boolean, - description: "Mute notifications in addition to statuses? Defaults to true.", - default: true - } - }, - example: %{ - "notifications" => true - } - }) -end diff --git a/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex b/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex deleted file mode 100644 index 960e14db1..000000000 --- a/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex +++ /dev/null @@ -1,58 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse do - require OpenApiSpex - - OpenApiSpex.schema(%{ - title: "AccountRelationshipsResponse", - description: "Response schema for account relationships", - type: :array, - items: Pleroma.Web.ApiSpec.Schemas.AccountRelationship, - example: [ - %{ - "id" => "1", - "following" => true, - "showing_reblogs" => true, - "followed_by" => true, - "blocking" => false, - "blocked_by" => true, - "muting" => false, - "muting_notifications" => false, - "requested" => false, - "domain_blocking" => false, - "subscribing" => false, - "endorsed" => true - }, - %{ - "id" => "2", - "following" => true, - "showing_reblogs" => true, - "followed_by" => true, - "blocking" => false, - "blocked_by" => true, - "muting" => true, - "muting_notifications" => false, - "requested" => true, - "domain_blocking" => false, - "subscribing" => false, - "endorsed" => false - }, - %{ - "id" => "3", - "following" => true, - "showing_reblogs" => true, - "followed_by" => true, - "blocking" => true, - "blocked_by" => false, - "muting" => true, - "muting_notifications" => false, - "requested" => false, - "domain_blocking" => true, - "subscribing" => true, - "endorsed" => false - } - ] - }) -end diff --git a/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex b/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex deleted file mode 100644 index 35220c78a..000000000 --- a/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex +++ /dev/null @@ -1,125 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest do - alias OpenApiSpex.Schema - alias Pleroma.Web.ApiSpec.Schemas.AccountAttributeField - alias Pleroma.Web.ApiSpec.Schemas.ActorType - alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope - require OpenApiSpex - - OpenApiSpex.schema(%{ - title: "AccountUpdateCredentialsRequest", - description: "POST body for creating an account", - type: :object, - properties: %{ - bot: %Schema{ - type: :boolean, - description: "Whether the account has a bot flag." - }, - display_name: %Schema{ - type: :string, - description: "The display name to use for the profile." - }, - note: %Schema{type: :string, description: "The account bio."}, - avatar: %Schema{ - type: :string, - description: "Avatar image encoded using multipart/form-data", - format: :binary - }, - header: %Schema{ - type: :string, - description: "Header image encoded using multipart/form-data", - format: :binary - }, - locked: %Schema{ - type: :boolean, - description: "Whether manual approval of follow requests is required." - }, - fields_attributes: %Schema{ - oneOf: [ - %Schema{type: :array, items: AccountAttributeField}, - %Schema{type: :object, additionalProperties: %Schema{type: AccountAttributeField}} - ] - }, - # NOTE: `source` field is not supported - # - # source: %Schema{ - # type: :object, - # properties: %{ - # privacy: %Schema{type: :string}, - # sensitive: %Schema{type: :boolean}, - # language: %Schema{type: :string} - # } - # }, - - # Pleroma-specific fields - no_rich_text: %Schema{ - type: :boolean, - description: "html tags are stripped from all statuses requested from the API" - }, - hide_followers: %Schema{type: :boolean, description: "user's followers will be hidden"}, - hide_follows: %Schema{type: :boolean, description: "user's follows will be hidden"}, - hide_followers_count: %Schema{ - type: :boolean, - description: "user's follower count will be hidden" - }, - hide_follows_count: %Schema{ - type: :boolean, - description: "user's follow count will be hidden" - }, - hide_favorites: %Schema{ - type: :boolean, - description: "user's favorites timeline will be hidden" - }, - show_role: %Schema{ - type: :boolean, - description: "user's role (e.g admin, moderator) will be exposed to anyone in the - API" - }, - default_scope: VisibilityScope, - pleroma_settings_store: %Schema{ - type: :object, - description: "Opaque user settings to be saved on the backend." - }, - skip_thread_containment: %Schema{ - type: :boolean, - description: "Skip filtering out broken threads" - }, - allow_following_move: %Schema{ - type: :boolean, - description: "Allows automatically follow moved following accounts" - }, - pleroma_background_image: %Schema{ - type: :string, - description: "Sets the background image of the user.", - format: :binary - }, - discoverable: %Schema{ - type: :boolean, - description: "Discovery of this account in search results and other services is allowed." - }, - actor_type: ActorType - }, - example: %{ - bot: false, - display_name: "cofe", - note: "foobar", - fields_attributes: [%{name: "foo", value: "bar"}], - no_rich_text: false, - hide_followers: true, - hide_follows: false, - hide_followers_count: false, - hide_follows_count: false, - hide_favorites: false, - show_role: false, - default_scope: "private", - pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}}, - skip_thread_containment: false, - allow_following_move: false, - discoverable: false, - actor_type: "Person" - } - }) -end diff --git a/lib/pleroma/web/api_spec/schemas/accounts_response.ex b/lib/pleroma/web/api_spec/schemas/accounts_response.ex deleted file mode 100644 index b714f59e7..000000000 --- a/lib/pleroma/web/api_spec/schemas/accounts_response.ex +++ /dev/null @@ -1,13 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.AccountsResponse do - require OpenApiSpex - - OpenApiSpex.schema(%{ - title: "AccountsResponse", - type: :array, - items: Pleroma.Web.ApiSpec.Schemas.Account - }) -end diff --git a/lib/pleroma/web/api_spec/schemas/api_error.ex b/lib/pleroma/web/api_spec/schemas/api_error.ex new file mode 100644 index 000000000..5815df94c --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/api_error.ex @@ -0,0 +1,19 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.ApiError do + alias OpenApiSpex.Schema + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "ApiError", + description: "Response schema for API error", + type: :object, + properties: %{error: %Schema{type: :string}}, + example: %{ + "error" => "Something went wrong" + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/custom_emoji.ex b/lib/pleroma/web/api_spec/schemas/custom_emoji.ex deleted file mode 100644 index 5531b2081..000000000 --- a/lib/pleroma/web/api_spec/schemas/custom_emoji.ex +++ /dev/null @@ -1,30 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.CustomEmoji do - alias OpenApiSpex.Schema - - require OpenApiSpex - - OpenApiSpex.schema(%{ - title: "CustomEmoji", - description: "Response schema for an CustomEmoji", - type: :object, - properties: %{ - shortcode: %Schema{type: :string}, - url: %Schema{type: :string}, - static_url: %Schema{type: :string}, - visible_in_picker: %Schema{type: :boolean}, - category: %Schema{type: :string}, - tags: %Schema{type: :array} - }, - example: %{ - "shortcode" => "aaaa", - "url" => "https://files.mastodon.social/custom_emojis/images/000/007/118/original/aaaa.png", - "static_url" => - "https://files.mastodon.social/custom_emojis/images/000/007/118/static/aaaa.png", - "visible_in_picker" => true - } - }) -end diff --git a/lib/pleroma/web/api_spec/schemas/emoji.ex b/lib/pleroma/web/api_spec/schemas/emoji.ex new file mode 100644 index 000000000..26f35e648 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/emoji.ex @@ -0,0 +1,29 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.Emoji do + alias OpenApiSpex.Schema + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "Emoji", + description: "Response schema for an emoji", + type: :object, + properties: %{ + shortcode: %Schema{type: :string}, + url: %Schema{type: :string, format: :uri}, + static_url: %Schema{type: :string, format: :uri}, + visible_in_picker: %Schema{type: :boolean} + }, + example: %{ + "shortcode" => "fatyoshi", + "url" => + "https://files.mastodon.social/custom_emojis/images/000/023/920/original/e57ecb623faa0dc9.png", + "static_url" => + "https://files.mastodon.social/custom_emojis/images/000/023/920/static/e57ecb623faa0dc9.png", + "visible_in_picker" => true + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/list.ex b/lib/pleroma/web/api_spec/schemas/list.ex deleted file mode 100644 index f85fac2b8..000000000 --- a/lib/pleroma/web/api_spec/schemas/list.ex +++ /dev/null @@ -1,23 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.List do - alias OpenApiSpex.Schema - - require OpenApiSpex - - OpenApiSpex.schema(%{ - title: "List", - description: "Response schema for a list", - type: :object, - properties: %{ - id: %Schema{type: :string}, - title: %Schema{type: :string} - }, - example: %{ - "id" => "123", - "title" => "my list" - } - }) -end diff --git a/lib/pleroma/web/api_spec/schemas/lists_response.ex b/lib/pleroma/web/api_spec/schemas/lists_response.ex deleted file mode 100644 index 132454579..000000000 --- a/lib/pleroma/web/api_spec/schemas/lists_response.ex +++ /dev/null @@ -1,16 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.ListsResponse do - alias Pleroma.Web.ApiSpec.Schemas.List - - require OpenApiSpex - - OpenApiSpex.schema(%{ - title: "ListsResponse", - description: "Response schema for lists", - type: :array, - items: List - }) -end diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex index 5fc9e889f..0474b550b 100644 --- a/lib/pleroma/web/api_spec/schemas/poll.ex +++ b/lib/pleroma/web/api_spec/schemas/poll.ex @@ -4,7 +4,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do alias OpenApiSpex.Schema - alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji + alias Pleroma.Web.ApiSpec.Schemas.Emoji alias Pleroma.Web.ApiSpec.Schemas.FlakeID require OpenApiSpex @@ -20,7 +20,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do multiple: %Schema{type: :boolean}, votes_count: %Schema{type: :integer}, voted: %Schema{type: :boolean}, - emojis: %Schema{type: :array, items: AccountEmoji}, + emojis: %Schema{type: :array, items: Emoji}, options: %Schema{ type: :array, items: %Schema{ diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index bf5f04691..aef0588d4 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -5,7 +5,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Schemas.Account - alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji + alias Pleroma.Web.ApiSpec.Schemas.Emoji alias Pleroma.Web.ApiSpec.Schemas.FlakeID alias Pleroma.Web.ApiSpec.Schemas.Poll alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope @@ -41,7 +41,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do }, content: %Schema{type: :string, format: :html}, created_at: %Schema{type: :string, format: "date-time"}, - emojis: %Schema{type: :array, items: AccountEmoji}, + emojis: %Schema{type: :array, items: Emoji}, favourited: %Schema{type: :boolean}, favourites_count: %Schema{type: :integer}, id: FlakeID, diff --git a/lib/pleroma/web/api_spec/schemas/statuses_response.ex b/lib/pleroma/web/api_spec/schemas/statuses_response.ex deleted file mode 100644 index fb7c7e0aa..000000000 --- a/lib/pleroma/web/api_spec/schemas/statuses_response.ex +++ /dev/null @@ -1,13 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.StatusesResponse do - require OpenApiSpex - - OpenApiSpex.schema(%{ - title: "StatusesResponse", - type: :array, - items: Pleroma.Web.ApiSpec.Schemas.Status - }) -end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index b1513001b..37adeec5f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -104,8 +104,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do :fullname ]) |> Map.put(:nickname, params.username) - |> Map.put(:fullname, params.fullname || params.username) - |> Map.put(:bio, params.bio || "") + |> Map.put(:fullname, Map.get(params, :fullname, params.username)) |> Map.put(:confirm, params.password) |> Map.put(:trusted_app, app.trusted) @@ -158,7 +157,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do params = params - |> Map.from_struct() |> Enum.filter(fn {_, value} -> not is_nil(value) end) |> Enum.into(%{}) @@ -217,11 +215,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do Enum.map(fields, fn {_, v} -> v end) else Enum.map(fields, fn - %Pleroma.Web.ApiSpec.Schemas.AccountAttributeField{} = field -> - %{"name" => field.name, "value" => field.value} - - field -> - field + %{} = field -> %{"name" => field.name, "value" => field.value} + field -> field end) end end -- cgit v1.2.3 From ea5142b94bcd1a02571776440dc828dd08a2c3d6 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Tue, 28 Apr 2020 09:32:43 +0300 Subject: convert markdown content to html --- lib/pleroma/web/activity_pub/transmogrifier.ex | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 17e3c203a..91933dc0f 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do A module to handle coding from internal to wire ActivityPub and back. """ alias Pleroma.Activity + alias Pleroma.EarmarkRenderer alias Pleroma.FollowingRelationship alias Pleroma.Object alias Pleroma.Object.Containment @@ -35,7 +36,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> fix_actor |> fix_url |> fix_attachments - |> fix_media_type |> fix_context |> fix_in_reply_to(options) |> fix_emoji @@ -44,6 +44,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> fix_addressing |> fix_summary |> fix_type(options) + |> fix_content end def fix_summary(%{"summary" => nil} = object) do @@ -358,11 +359,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_type(object, _), do: object - defp fix_media_type(%{"mediaType" => _} = object) do - Map.put(object, "mediaType", "text/html") + defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = object) + when is_binary(content) do + html_content = + content + |> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer}) + |> Pleroma.HTML.filter_tags() + + Map.merge(object, %{"content" => html_content, "mediaType" => "text/html"}) end - defp fix_media_type(object), do: object + defp fix_content(object), do: object defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do with true <- id =~ "follows", -- cgit v1.2.3 From 560f2c1979ca4d49f18abd6de6aac49875bfc771 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 28 Apr 2020 16:50:37 +0400 Subject: Add OpenAPI spec for ReportController --- .../web/api_spec/operations/report_operation.ex | 78 ++++++++++++++++++++++ lib/pleroma/web/common_api/common_api.ex | 10 ++- lib/pleroma/web/common_api/utils.ex | 3 +- .../mastodon_api/controllers/report_controller.ex | 6 +- 4 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 lib/pleroma/web/api_spec/operations/report_operation.ex (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/report_operation.ex b/lib/pleroma/web/api_spec/operations/report_operation.ex new file mode 100644 index 000000000..da4d50703 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/report_operation.ex @@ -0,0 +1,78 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.ReportOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Helpers + alias Pleroma.Web.ApiSpec.Schemas.ApiError + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def create_operation do + %Operation{ + tags: ["reports"], + summary: "File a report", + description: "Report problematic users to your moderators", + operationId: "ReportController.create", + security: [%{"oAuth" => ["follow", "write:reports"]}], + requestBody: Helpers.request_body("Parameters", create_request(), required: true), + responses: %{ + 200 => Operation.response("Report", "application/json", create_response()), + 400 => Operation.response("Report", "application/json", ApiError) + } + } + end + + defp create_request do + %Schema{ + title: "ReportCreateRequest", + description: "POST body for creating a report", + type: :object, + properties: %{ + account_id: %Schema{type: :string, description: "ID of the account to report"}, + status_ids: %Schema{ + type: :array, + items: %Schema{type: :string}, + description: "Array of Statuses to attach to the report, for context" + }, + comment: %Schema{ + type: :string, + description: "Reason for the report" + }, + forward: %Schema{ + type: :boolean, + default: false, + description: + "If the account is remote, should the report be forwarded to the remote admin?" + } + }, + required: [:account_id], + example: %{ + "account_id" => "123", + "status_ids" => ["1337"], + "comment" => "bad status!", + "forward" => "false" + } + } + end + + defp create_response do + %Schema{ + title: "ReportResponse", + type: :object, + properties: %{ + id: %Schema{type: :string, description: "Report ID"}, + action_taken: %Schema{type: :boolean, description: "Is action taken?"} + }, + example: %{ + "id" => "123", + "action_taken" => false + } + } + end +end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index d1efe0c36..53ce7d425 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -380,9 +380,9 @@ defmodule Pleroma.Web.CommonAPI do ThreadMute.exists?(user.id, activity.data["context"]) end - def report(user, %{"account_id" => account_id} = data) do - with {:ok, account} <- get_reported_account(account_id), - {:ok, {content_html, _, _}} <- make_report_content_html(data["comment"]), + def report(user, data) do + with {:ok, account} <- get_reported_account(data.account_id), + {:ok, {content_html, _, _}} <- make_report_content_html(data[:comment]), {:ok, statuses} <- get_report_statuses(account, data) do ActivityPub.flag(%{ context: Utils.generate_context_id(), @@ -390,13 +390,11 @@ defmodule Pleroma.Web.CommonAPI do account: account, statuses: statuses, content: content_html, - forward: data["forward"] || false + forward: Map.get(data, :forward, false) }) end end - def report(_user, _params), do: {:error, dgettext("errors", "Valid `account_id` required")} - defp get_reported_account(account_id) do case User.get_cached_by_id(account_id) do %User{} = account -> {:ok, account} diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 945e63e22..6540fa5d1 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -504,7 +504,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do end end - def get_report_statuses(%User{ap_id: actor}, %{"status_ids" => status_ids}) do + def get_report_statuses(%User{ap_id: actor}, %{status_ids: status_ids}) + when is_list(status_ids) do {:ok, Activity.all_by_actor_and_id(actor, status_ids)} end diff --git a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex index f5782be13..85bd52106 100644 --- a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex @@ -9,12 +9,14 @@ defmodule Pleroma.Web.MastodonAPI.ReportController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ReportOperation + @doc "POST /api/v1/reports" - def create(%{assigns: %{user: user}} = conn, params) do + def create(%{assigns: %{user: user}, body_params: params} = conn, _) do with {:ok, activity} <- Pleroma.Web.CommonAPI.report(user, params) do render(conn, "show.json", activity: activity) end -- cgit v1.2.3 From 4c0e53367acd74de04de070a5e33380f5e457163 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Tue, 28 Apr 2020 20:04:25 +0300 Subject: [#2349] Post-merge fix. --- lib/pleroma/web/api_spec/operations/account_operation.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 2efe6e901..64e2e43c4 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -294,13 +294,13 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do } end - def follows_operation do + def follow_by_uri_operation do %Operation{ tags: ["accounts"], - summary: "Follows", + summary: "Follow by URI", operationId: "AccountController.follows", security: [%{"oAuth" => ["follow", "write:follows"]}], - requestBody: request_body("Parameters", follows_request(), required: true), + requestBody: request_body("Parameters", follow_by_uri_request(), required: true), responses: %{ 200 => Operation.response("Account", "application/json", AccountRelationship), 400 => Operation.response("Error", "application/json", ApiError), @@ -615,7 +615,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do } end - defp follows_request do + defp follow_by_uri_request do %Schema{ title: "AccountFollowsRequest", description: "POST body for muting an account", -- cgit v1.2.3 From 5238ae3dd3bfba9ff84d5f47e2419227fc7c5d9a Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 28 Apr 2020 21:27:54 +0400 Subject: Add OpenAPI spec for NotificationController --- lib/pleroma/web/api_spec/helpers.ex | 8 + .../web/api_spec/operations/account_operation.ex | 4 +- .../api_spec/operations/domain_block_operation.ex | 8 +- .../api_spec/operations/notification_operation.ex | 202 +++++++++++++++++++++ .../controllers/notification_controller.ex | 24 ++- lib/pleroma/web/router.ex | 2 +- 6 files changed, 233 insertions(+), 15 deletions(-) create mode 100644 lib/pleroma/web/api_spec/operations/notification_operation.ex (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex index ce40fb9e8..df0804486 100644 --- a/lib/pleroma/web/api_spec/helpers.ex +++ b/lib/pleroma/web/api_spec/helpers.ex @@ -46,4 +46,12 @@ defmodule Pleroma.Web.ApiSpec.Helpers do ) ] end + + def empty_object_response do + Operation.response("Empty object", "application/json", %Schema{type: :object, example: %{}}) + end + + def empty_array_response do + Operation.response("Empty array", "application/json", %Schema{type: :array, example: []}) + end end diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 2efe6e901..6fb6e627b 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -343,7 +343,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do description: "Not implemented", security: [%{"oAuth" => ["read:accounts"]}], responses: %{ - 200 => Operation.response("Empry array", "application/json", %Schema{type: :array}) + 200 => empty_array_response() } } end @@ -355,7 +355,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do operationId: "AccountController.identity_proofs", description: "Not implemented", responses: %{ - 200 => Operation.response("Empry array", "application/json", %Schema{type: :array}) + 200 => empty_array_response() } } end diff --git a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex index 3b7f51ceb..049bcf931 100644 --- a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex +++ b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex @@ -5,7 +5,7 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do alias OpenApiSpex.Operation alias OpenApiSpex.Schema - alias Pleroma.Web.ApiSpec.Helpers + import Pleroma.Web.ApiSpec.Helpers def open_api_operation(action) do operation = String.to_existing_atom("#{action}_operation") @@ -46,9 +46,7 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do operationId: "DomainBlockController.create", requestBody: domain_block_request(), security: [%{"oAuth" => ["follow", "write:blocks"]}], - responses: %{ - 200 => Operation.response("Empty object", "application/json", %Schema{type: :object}) - } + responses: %{200 => empty_object_response()} } end @@ -67,7 +65,7 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do end defp domain_block_request do - Helpers.request_body( + request_body( "Parameters", %Schema{ type: :object, diff --git a/lib/pleroma/web/api_spec/operations/notification_operation.ex b/lib/pleroma/web/api_spec/operations/notification_operation.ex new file mode 100644 index 000000000..c6514f3f2 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/notification_operation.ex @@ -0,0 +1,202 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.NotificationOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.Account + alias Pleroma.Web.ApiSpec.Schemas.ApiError + alias Pleroma.Web.ApiSpec.Schemas.BooleanLike + alias Pleroma.Web.ApiSpec.Schemas.Status + alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + tags: ["Notifications"], + summary: "Get all notifications", + description: + "Notifications concerning the user. This API returns Link headers containing links to the next/previous page. However, the links can also be constructed dynamically using query params and `id` values.", + operationId: "NotificationController.index", + security: [%{"oAuth" => ["read:notifications"]}], + parameters: + [ + Operation.parameter( + :exclude_types, + :query, + %Schema{type: :array, items: notification_type()}, + "Array of types to exclude" + ), + Operation.parameter( + :account_id, + :query, + %Schema{type: :string}, + "Return only notifications received from this account" + ), + Operation.parameter( + :exclude_visibilities, + :query, + %Schema{type: :array, items: VisibilityScope}, + "Exclude the notifications for activities with the given visibilities" + ), + Operation.parameter( + :include_types, + :query, + %Schema{type: :array, items: notification_type()}, + "Include the notifications for activities with the given types" + ), + Operation.parameter( + :with_muted, + :query, + BooleanLike, + "Include the notifications from muted users" + ) + ] ++ pagination_params(), + responses: %{ + 200 => + Operation.response("Array of notifications", "application/json", %Schema{ + type: :array, + items: notification() + }), + 404 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def show_operation do + %Operation{ + tags: ["Notifications"], + summary: "Get a single notification", + description: "View information about a notification with a given ID.", + operationId: "NotificationController.show", + security: [%{"oAuth" => ["read:notifications"]}], + parameters: [id_param()], + responses: %{ + 200 => Operation.response("Notification", "application/json", notification()) + } + } + end + + def clear_operation do + %Operation{ + tags: ["Notifications"], + summary: "Dismiss all notifications", + description: "Clear all notifications from the server.", + operationId: "NotificationController.clear", + security: [%{"oAuth" => ["write:notifications"]}], + responses: %{200 => empty_object_response()} + } + end + + def dismiss_operation do + %Operation{ + tags: ["Notifications"], + summary: "Dismiss a single notification", + description: "Clear a single notification from the server.", + operationId: "NotificationController.dismiss", + parameters: [id_param()], + security: [%{"oAuth" => ["write:notifications"]}], + responses: %{200 => empty_object_response()} + } + end + + def dismiss_via_body_operation do + %Operation{ + tags: ["Notifications"], + summary: "Dismiss a single notification", + deprecated: true, + description: "Clear a single notification from the server.", + operationId: "NotificationController.dismiss_via_body", + requestBody: + request_body( + "Parameters", + %Schema{type: :object, properties: %{id: %Schema{type: :string}}}, + required: true + ), + security: [%{"oAuth" => ["write:notifications"]}], + responses: %{200 => empty_object_response()} + } + end + + def destroy_multiple_operation do + %Operation{ + tags: ["Notifications"], + summary: "Dismiss multiple notifications", + operationId: "NotificationController.destroy_multiple", + security: [%{"oAuth" => ["write:notifications"]}], + parameters: [ + Operation.parameter( + :ids, + :query, + %Schema{type: :array, items: %Schema{type: :string}}, + "Array of notification IDs to dismiss", + required: true + ) + ], + responses: %{200 => empty_object_response()} + } + end + + defp notification do + %Schema{ + title: "Notification", + description: "Response schema for a notification", + type: :object, + properties: %{ + id: %Schema{type: :string}, + type: notification_type(), + created_at: %Schema{type: :string, format: :"date-time"}, + account: %Schema{ + allOf: [Account], + description: "The account that performed the action that generated the notification." + }, + status: %Schema{ + allOf: [Status], + description: + "Status that was the object of the notification, e.g. in mentions, reblogs, favourites, or polls.", + nullable: true + } + }, + example: %{ + "id" => "34975861", + "type" => "mention", + "created_at" => "2019-11-23T07:49:02.064Z", + "account" => Account.schema().example, + "status" => Status.schema().example + } + } + end + + defp notification_type do + %Schema{ + type: :string, + enum: ["follow", "favourite", "reblog", "mention", "poll", "pleroma:emoji_reaction", "move"], + description: """ + The type of event that resulted in the notification. + + - `follow` - Someone followed you + - `mention` - Someone mentioned you in their status + - `reblog` - Someone boosted one of your statuses + - `favourite` - Someone favourited one of your statuses + - `poll` - A poll you have voted in or created has ended + - `move` - Someone moved their account + - `pleroma:emoji_reaction` - Someone reacted with emoji to your status + """ + } + end + + defp id_param do + Operation.parameter(:id, :path, :string, "Notification ID", + example: "123", + required: true + ) + end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex index 7fb536b09..dcb421756 100644 --- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -13,6 +13,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do @oauth_read_actions [:show, :index] + plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) + plug( OAuthScopesPlug, %{scopes: ["read:notifications"]} when action in @oauth_read_actions @@ -22,14 +24,16 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.NotificationOperation + # GET /api/v1/notifications - def index(conn, %{"account_id" => account_id} = params) do + def index(conn, %{account_id: account_id} = params) do case Pleroma.User.get_cached_by_id(account_id) do %{ap_id: account_ap_id} -> params = params - |> Map.delete("account_id") - |> Map.put("account_ap_id", account_ap_id) + |> Map.delete(:account_id) + |> Map.put(:account_ap_id, account_ap_id) index(conn, params) @@ -41,6 +45,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do end def index(%{assigns: %{user: user}} = conn, params) do + params = Map.new(params, fn {k, v} -> {to_string(k), v} end) notifications = MastodonAPI.get_notifications(user, params) conn @@ -53,7 +58,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do end # GET /api/v1/notifications/:id - def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do + def show(%{assigns: %{user: user}} = conn, %{id: id}) do with {:ok, notification} <- Notification.get(user, id) do render(conn, "show.json", notification: notification, for: user) else @@ -71,8 +76,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do end # POST /api/v1/notifications/:id/dismiss - # POST /api/v1/notifications/dismiss (deprecated) - def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do + + def dismiss(%{assigns: %{user: user}} = conn, %{id: id} = _params) do with {:ok, _notif} <- Notification.dismiss(user, id) do json(conn, %{}) else @@ -83,8 +88,13 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do end end + # POST /api/v1/notifications/dismiss (deprecated) + def dismiss_via_body(%{body_params: params} = conn, _) do + dismiss(conn, params) + end + # DELETE /api/v1/notifications/destroy_multiple - def destroy_multiple(%{assigns: %{user: user}} = conn, %{"ids" => ids} = _params) do + def destroy_multiple(%{assigns: %{user: user}} = conn, %{ids: ids} = _params) do Notification.destroy_multiple(user, ids) json(conn, %{}) end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 153802a43..fe984b06c 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -344,7 +344,7 @@ defmodule Pleroma.Web.Router do post("/notifications/clear", NotificationController, :clear) delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple) # Deprecated: was removed in Mastodon v3, use `/notifications/:id/dismiss` instead - post("/notifications/dismiss", NotificationController, :dismiss) + post("/notifications/dismiss", NotificationController, :dismiss_via_body) get("/scheduled_statuses", ScheduledActivityController, :index) get("/scheduled_statuses/:id", ScheduledActivityController, :show) -- cgit v1.2.3 From 2f77842bd3c07d915cd341f7d3ba1f9053cc3a18 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 29 Apr 2020 14:31:13 +0400 Subject: Fix account registration when captcha is enabled but not provided --- lib/pleroma/web/twitter_api/twitter_api.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index cf1d9c74c..a6ef9a310 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -47,9 +47,9 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do :ok else Pleroma.Captcha.validate( - params.captcha_token, - params.captcha_solution, - params.captcha_answer_data + params[:captcha_token], + params[:captcha_solution], + params[:captcha_answer_data] ) end end -- cgit v1.2.3 From 7b0c8f0fde495e50c5434575c452addeace39e60 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 29 Apr 2020 20:48:08 +0400 Subject: Add tests for account registration with captcha enabled and improve errors --- lib/pleroma/captcha/captcha.ex | 99 +++++++++++-------- lib/pleroma/captcha/kocaptcha.ex | 5 +- lib/pleroma/captcha/native.ex | 5 +- .../mastodon_api/controllers/account_controller.ex | 24 +---- lib/pleroma/web/twitter_api/twitter_api.ex | 108 +++++++++++++-------- 5 files changed, 134 insertions(+), 107 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/captcha/captcha.ex b/lib/pleroma/captcha/captcha.ex index cf75c3adc..e17dc2426 100644 --- a/lib/pleroma/captcha/captcha.ex +++ b/lib/pleroma/captcha/captcha.ex @@ -3,8 +3,6 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Captcha do - import Pleroma.Web.Gettext - alias Calendar.DateTime alias Plug.Crypto.KeyGenerator alias Plug.Crypto.MessageEncryptor @@ -37,19 +35,14 @@ defmodule Pleroma.Captcha do @doc false def handle_call(:new, _from, state) do - enabled = Pleroma.Config.get([__MODULE__, :enabled]) - - if !enabled do + if not enabled?() do {:reply, %{type: :none}, state} else new_captcha = method().new() - secret_key_base = Pleroma.Config.get!([Pleroma.Web.Endpoint, :secret_key_base]) - # This make salt a little different for two keys - token = new_captcha[:token] - secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt") - sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign") + {secret, sign_secret} = secret_pair(new_captcha[:token]) + # Basically copy what Phoenix.Token does here, add the time to # the actual data and make it a binary to then encrypt it encrypted_captcha_answer = @@ -71,44 +64,68 @@ defmodule Pleroma.Captcha do @doc false def handle_call({:validate, token, captcha, answer_data}, _from, state) do + {:reply, do_validate(token, captcha, answer_data), state} + end + + def enabled?, do: Pleroma.Config.get([__MODULE__, :enabled], false) + + defp seconds_valid, do: Pleroma.Config.get!([__MODULE__, :seconds_valid]) + + defp secret_pair(token) do secret_key_base = Pleroma.Config.get!([Pleroma.Web.Endpoint, :secret_key_base]) secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt") sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign") + {secret, sign_secret} + end + + defp do_validate(token, captcha, answer_data) do + with {:ok, %{at: at, answer_data: answer_md5}} <- validate_answer_data(token, answer_data), + :ok <- validate_expiration(at), + :ok <- validate_usage(token), + :ok <- method().validate(token, captcha, answer_md5), + {:ok, _} <- mark_captcha_as_used(token) do + :ok + end + end + + defp validate_answer_data(token, answer_data) do + {secret, sign_secret} = secret_pair(token) + + with false <- is_nil(answer_data), + {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret), + %{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do + {:ok, %{at: at, answer_data: answer_md5}} + else + _ -> {:error, :invalid_answer_data} + end + end + + defp validate_expiration(created_at) do # If the time found is less than (current_time-seconds_valid) then the time has already passed # Later we check that the time found is more than the presumed invalidatation time, that means # that the data is still valid and the captcha can be checked - seconds_valid = Pleroma.Config.get!([Pleroma.Captcha, :seconds_valid]) - valid_if_after = DateTime.subtract!(DateTime.now_utc(), seconds_valid) - - result = - with false <- is_nil(answer_data), - {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret), - %{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do - try do - if DateTime.before?(at, valid_if_after), - do: throw({:error, dgettext("errors", "CAPTCHA expired")}) - - if not is_nil(Cachex.get!(:used_captcha_cache, token)), - do: throw({:error, dgettext("errors", "CAPTCHA already used")}) - - res = method().validate(token, captcha, answer_md5) - # Throw if an error occurs - if res != :ok, do: throw(res) - - # Mark this captcha as used - {:ok, _} = - Cachex.put(:used_captcha_cache, token, true, ttl: :timer.seconds(seconds_valid)) - - :ok - catch - :throw, e -> e - end - else - _ -> {:error, dgettext("errors", "Invalid answer data")} - end - - {:reply, result, state} + + valid_if_after = DateTime.subtract!(DateTime.now_utc(), seconds_valid()) + + if DateTime.before?(created_at, valid_if_after) do + {:error, :expired} + else + :ok + end + end + + defp validate_usage(token) do + if is_nil(Cachex.get!(:used_captcha_cache, token)) do + :ok + else + {:error, :already_used} + end + end + + defp mark_captcha_as_used(token) do + ttl = seconds_valid() |> :timer.seconds() + Cachex.put(:used_captcha_cache, token, true, ttl: ttl) end defp method, do: Pleroma.Config.get!([__MODULE__, :method]) diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex index 06ceb20b6..6bc2fa158 100644 --- a/lib/pleroma/captcha/kocaptcha.ex +++ b/lib/pleroma/captcha/kocaptcha.ex @@ -3,7 +3,6 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Captcha.Kocaptcha do - import Pleroma.Web.Gettext alias Pleroma.Captcha.Service @behaviour Service @@ -13,7 +12,7 @@ defmodule Pleroma.Captcha.Kocaptcha do case Tesla.get(endpoint <> "/new") do {:error, _} -> - %{error: dgettext("errors", "Kocaptcha service unavailable")} + %{error: :kocaptcha_service_unavailable} {:ok, res} -> json_resp = Jason.decode!(res.body) @@ -33,6 +32,6 @@ defmodule Pleroma.Captcha.Kocaptcha do if not is_nil(captcha) and :crypto.hash(:md5, captcha) |> Base.encode16() == String.upcase(answer_data), do: :ok, - else: {:error, dgettext("errors", "Invalid CAPTCHA")} + else: {:error, :invalid} end end diff --git a/lib/pleroma/captcha/native.ex b/lib/pleroma/captcha/native.ex index 06c479ca9..a90631d61 100644 --- a/lib/pleroma/captcha/native.ex +++ b/lib/pleroma/captcha/native.ex @@ -3,7 +3,6 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Captcha.Native do - import Pleroma.Web.Gettext alias Pleroma.Captcha.Service @behaviour Service @@ -11,7 +10,7 @@ defmodule Pleroma.Captcha.Native do def new do case Captcha.get() do :error -> - %{error: dgettext("errors", "Captcha error")} + %{error: :captcha_error} {:ok, answer_data, img_binary} -> %{ @@ -25,7 +24,7 @@ defmodule Pleroma.Captcha.Native do @impl Service def validate(_token, captcha, captcha) when not is_nil(captcha), do: :ok - def validate(_token, _captcha, _answer), do: {:error, dgettext("errors", "Invalid CAPTCHA")} + def validate(_token, _captcha, _answer), do: {:error, :invalid} defp token do 10 diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 1eedf02d6..61b0e2f63 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -94,24 +94,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "POST /api/v1/accounts" def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do - params = - params - |> Map.take([ - :email, - :bio, - :captcha_solution, - :captcha_token, - :captcha_answer_data, - :token, - :password, - :fullname - ]) - |> Map.put(:nickname, params.username) - |> Map.put(:fullname, Map.get(params, :fullname, params.username)) - |> Map.put(:confirm, params.password) - |> Map.put(:trusted_app, app.trusted) - with :ok <- validate_email_param(params), + :ok <- TwitterAPI.validate_captcha(app, params), {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true), {:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do json(conn, %{ @@ -121,7 +105,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do created_at: Token.Utils.format_created_at(token) }) else - {:error, errors} -> json_response(conn, :bad_request, errors) + {:error, error} -> json_response(conn, :bad_request, %{error: error}) end end @@ -133,11 +117,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do render_error(conn, :forbidden, "Invalid credentials") end - defp validate_email_param(%{:email => email}) when not is_nil(email), do: :ok + defp validate_email_param(%{email: email}) when not is_nil(email), do: :ok defp validate_email_param(_) do case Pleroma.Config.get([:instance, :account_activation_required]) do - true -> {:error, %{"error" => "Missing parameters"}} + true -> {:error, dgettext("errors", "Missing parameter: %{name}", name: "email")} _ -> :ok end end diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index a6ef9a310..5cfb385ac 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -3,54 +3,27 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TwitterAPI.TwitterAPI do + import Pleroma.Web.Gettext + alias Pleroma.Emails.Mailer alias Pleroma.Emails.UserEmail alias Pleroma.Repo alias Pleroma.User alias Pleroma.UserInviteToken - require Pleroma.Constants - def register_user(params, opts \\ []) do params = params - |> Map.take([ - :nickname, - :password, - :captcha_solution, - :captcha_token, - :captcha_answer_data, - :token, - :email, - :trusted_app - ]) - |> Map.put(:bio, User.parse_bio(params[:bio] || "")) - |> Map.put(:name, params.fullname) - |> Map.put(:password_confirmation, params[:confirm]) - - case validate_captcha(params) do - :ok -> - if Pleroma.Config.get([:instance, :registrations_open]) do - create_user(params, opts) - else - create_user_with_invite(params, opts) - end + |> Map.take([:email, :token, :password]) + |> Map.put(:bio, params |> Map.get(:bio, "") |> User.parse_bio()) + |> Map.put(:nickname, params[:username]) + |> Map.put(:name, Map.get(params, :fullname, params[:username])) + |> Map.put(:password_confirmation, params[:password]) - {:error, error} -> - # I have no idea how this error handling works - {:error, %{error: Jason.encode!(%{captcha: [error]})}} - end - end - - defp validate_captcha(params) do - if params[:trusted_app] || not Pleroma.Config.get([Pleroma.Captcha, :enabled]) do - :ok + if Pleroma.Config.get([:instance, :registrations_open]) do + create_user(params, opts) else - Pleroma.Captcha.validate( - params[:captcha_token], - params[:captcha_solution], - params[:captcha_answer_data] - ) + create_user_with_invite(params, opts) end end @@ -75,16 +48,17 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do {:error, changeset} -> errors = - Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end) + changeset + |> Ecto.Changeset.traverse_errors(fn {msg, _opts} -> msg end) |> Jason.encode!() - {:error, %{error: errors}} + {:error, errors} end end def password_reset(nickname_or_email) do with true <- is_binary(nickname_or_email), - %User{local: true, email: email} = user when not is_nil(email) <- + %User{local: true, email: email} = user when is_binary(email) <- User.get_by_nickname_or_email(nickname_or_email), {:ok, token_record} <- Pleroma.PasswordResetToken.create_token(user) do user @@ -106,4 +80,58 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do {:error, "unknown user"} end end + + def validate_captcha(app, params) do + if app.trusted || not Pleroma.Captcha.enabled?() do + :ok + else + do_validate_captcha(params) + end + end + + defp do_validate_captcha(params) do + with :ok <- validate_captcha_presence(params), + :ok <- + Pleroma.Captcha.validate( + params[:captcha_token], + params[:captcha_solution], + params[:captcha_answer_data] + ) do + :ok + else + {:error, :captcha_error} -> + captcha_error(dgettext("errors", "CAPTCHA Error")) + + {:error, :invalid} -> + captcha_error(dgettext("errors", "Invalid CAPTCHA")) + + {:error, :kocaptcha_service_unavailable} -> + captcha_error(dgettext("errors", "Kocaptcha service unavailable")) + + {:error, :expired} -> + captcha_error(dgettext("errors", "CAPTCHA expired")) + + {:error, :already_used} -> + captcha_error(dgettext("errors", "CAPTCHA already used")) + + {:error, :invalid_answer_data} -> + captcha_error(dgettext("errors", "Invalid answer data")) + + {:error, error} -> + captcha_error(error) + end + end + + defp validate_captcha_presence(params) do + [:captcha_solution, :captcha_token, :captcha_answer_data] + |> Enum.find_value(:ok, fn key -> + unless is_binary(params[key]) do + error = dgettext("errors", "Invalid CAPTCHA (Missing parameter: %{name})", name: key) + {:error, error} + end + end) + end + + # For some reason FE expects error message to be a serialized JSON + defp captcha_error(error), do: {:error, Jason.encode!(%{captcha: [error]})} end -- cgit v1.2.3 From 39a78998d0a729323406497332f9402301994811 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 29 Apr 2020 21:01:16 +0400 Subject: Change Pleroma.CaptchaTest to be a regular module instead of GenServer --- lib/pleroma/application.ex | 1 - lib/pleroma/captcha/captcha.ex | 58 ++++++++++-------------------------------- 2 files changed, 14 insertions(+), 45 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index a00938c04..308d8cffa 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -73,7 +73,6 @@ defmodule Pleroma.Application do Pleroma.Repo, Config.TransferTask, Pleroma.Emoji, - Pleroma.Captcha, Pleroma.Plugs.RateLimiter.Supervisor ] ++ cachex_children() ++ diff --git a/lib/pleroma/captcha/captcha.ex b/lib/pleroma/captcha/captcha.ex index e17dc2426..6ab754b6f 100644 --- a/lib/pleroma/captcha/captcha.ex +++ b/lib/pleroma/captcha/captcha.ex @@ -7,36 +7,12 @@ defmodule Pleroma.Captcha do alias Plug.Crypto.KeyGenerator alias Plug.Crypto.MessageEncryptor - use GenServer - - @doc false - def start_link(_) do - GenServer.start_link(__MODULE__, [], name: __MODULE__) - end - - @doc false - def init(_) do - {:ok, nil} - end - @doc """ Ask the configured captcha service for a new captcha """ def new do - GenServer.call(__MODULE__, :new) - end - - @doc """ - Ask the configured captcha service to validate the captcha - """ - def validate(token, captcha, answer_data) do - GenServer.call(__MODULE__, {:validate, token, captcha, answer_data}) - end - - @doc false - def handle_call(:new, _from, state) do if not enabled?() do - {:reply, %{type: :none}, state} + %{type: :none} else new_captcha = method().new() @@ -53,18 +29,22 @@ defmodule Pleroma.Captcha do |> :erlang.term_to_binary() |> MessageEncryptor.encrypt(secret, sign_secret) - { - :reply, - # Replace the answer with the encrypted answer - %{new_captcha | answer_data: encrypted_captcha_answer}, - state - } + # Replace the answer with the encrypted answer + %{new_captcha | answer_data: encrypted_captcha_answer} end end - @doc false - def handle_call({:validate, token, captcha, answer_data}, _from, state) do - {:reply, do_validate(token, captcha, answer_data), state} + @doc """ + Ask the configured captcha service to validate the captcha + """ + def validate(token, captcha, answer_data) do + with {:ok, %{at: at, answer_data: answer_md5}} <- validate_answer_data(token, answer_data), + :ok <- validate_expiration(at), + :ok <- validate_usage(token), + :ok <- method().validate(token, captcha, answer_md5), + {:ok, _} <- mark_captcha_as_used(token) do + :ok + end end def enabled?, do: Pleroma.Config.get([__MODULE__, :enabled], false) @@ -79,16 +59,6 @@ defmodule Pleroma.Captcha do {secret, sign_secret} end - defp do_validate(token, captcha, answer_data) do - with {:ok, %{at: at, answer_data: answer_md5}} <- validate_answer_data(token, answer_data), - :ok <- validate_expiration(at), - :ok <- validate_usage(token), - :ok <- method().validate(token, captcha, answer_md5), - {:ok, _} <- mark_captcha_as_used(token) do - :ok - end - end - defp validate_answer_data(token, answer_data) do {secret, sign_secret} = secret_pair(token) -- cgit v1.2.3 From 8cf3a32463856f91a4e64a5cd33b5538b67c25c9 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Thu, 30 Apr 2020 00:49:59 +0300 Subject: Add exlude_replies to OpenAPI spec for account timelines --- lib/pleroma/web/api_spec/operations/account_operation.ex | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 64e2e43c4..d3e8bd484 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -131,6 +131,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do "Include statuses from muted acccounts." ), Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"), + Operation.parameter(:exclude_replies, :query, BooleanLike, "Exclude replies"), Operation.parameter( :exclude_visibilities, :query, -- cgit v1.2.3 From 5839e67eb86d6d14b21222247ce8e113c3b26637 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 6 Feb 2020 18:01:12 +0300 Subject: return data only for updated emoji --- .../controllers/emoji_api_controller.ex | 89 +++++++++++++--------- 1 file changed, 51 insertions(+), 38 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index e01825b48..981bac4fa 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -385,23 +385,35 @@ keeping it in cache for #{div(cache_ms, 1000)}s") json(conn, new_data) end - defp get_filename(%{"filename" => filename}), do: filename + defp get_filename(%Plug.Upload{filename: filename}), do: filename + defp get_filename(url) when is_binary(url), do: Path.basename(url) - defp get_filename(%{"file" => file}) do - case file do - %Plug.Upload{filename: filename} -> filename - url when is_binary(url) -> Path.basename(url) + defp empty?(str), do: String.trim(str) == "" + + defp update_pack_file(updated_full_pack, pack_file_p) do + content = Jason.encode!(updated_full_pack, pretty: true) + + File.write!(pack_file_p, content) + end + + defp create_subdirs(file_path) do + if String.contains?(file_path, "/") do + file_path + |> Path.dirname() + |> File.mkdir_p!() end end - defp empty?(str), do: String.trim(str) == "" + defp pack_info(pack_name) do + dir = Path.join(emoji_dir_path(), pack_name) + json_path = Path.join(dir, "pack.json") - defp update_file_and_send(conn, updated_full_pack, pack_file_p) do - # Write the emoji pack file - File.write!(pack_file_p, Jason.encode!(updated_full_pack, pretty: true)) + json = + json_path + |> File.read!() + |> Jason.decode!() - # Return the modified file list - json(conn, updated_full_pack["files"]) + {dir, json_path, json} end @doc """ @@ -422,23 +434,25 @@ keeping it in cache for #{div(cache_ms, 1000)}s") # Add def update_file( conn, - %{"pack_name" => pack_name, "action" => "add", "shortcode" => shortcode} = params + %{"pack_name" => pack_name, "action" => "add"} = params ) do - pack_dir = Path.join(emoji_dir_path(), pack_name) - pack_file_p = Path.join(pack_dir, "pack.json") + shortcode = + if params["shortcode"] do + params["shortcode"] + else + filename = get_filename(params["file"]) + Path.basename(filename, Path.extname(filename)) + end - full_pack = Jason.decode!(File.read!(pack_file_p)) + {pack_dir, pack_file_p, full_pack} = pack_info(pack_name) with {_, false} <- {:has_shortcode, Map.has_key?(full_pack["files"], shortcode)}, - filename <- get_filename(params), + filename <- params["filename"] || get_filename(params["file"]), false <- empty?(shortcode), - false <- empty?(filename) do - file_path = Path.join(pack_dir, filename) - + false <- empty?(filename), + file_path <- Path.join(pack_dir, filename) do # If the name contains directories, create them - if String.contains?(file_path, "/") do - File.mkdir_p!(Path.dirname(file_path)) - end + create_subdirs(file_path) case params["file"] do %Plug.Upload{path: upload_path} -> @@ -451,8 +465,11 @@ keeping it in cache for #{div(cache_ms, 1000)}s") File.write!(file_path, file_contents) end - updated_full_pack = put_in(full_pack, ["files", shortcode], filename) - update_file_and_send(conn, updated_full_pack, pack_file_p) + full_pack + |> put_in(["files", shortcode], filename) + |> update_pack_file(pack_file_p) + + json(conn, %{shortcode => filename}) else {:has_shortcode, _} -> conn @@ -472,10 +489,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") "action" => "remove", "shortcode" => shortcode }) do - pack_dir = Path.join(emoji_dir_path(), pack_name) - pack_file_p = Path.join(pack_dir, "pack.json") - - full_pack = Jason.decode!(File.read!(pack_file_p)) + {pack_dir, pack_file_p, full_pack} = pack_info(pack_name) if Map.has_key?(full_pack["files"], shortcode) do {emoji_file_path, updated_full_pack} = pop_in(full_pack, ["files", shortcode]) @@ -494,7 +508,8 @@ keeping it in cache for #{div(cache_ms, 1000)}s") end end - update_file_and_send(conn, updated_full_pack, pack_file_p) + update_pack_file(updated_full_pack, pack_file_p) + json(conn, %{shortcode => full_pack["files"][shortcode]}) else conn |> put_status(:bad_request) @@ -507,10 +522,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") conn, %{"pack_name" => pack_name, "action" => "update", "shortcode" => shortcode} = params ) do - pack_dir = Path.join(emoji_dir_path(), pack_name) - pack_file_p = Path.join(pack_dir, "pack.json") - - full_pack = Jason.decode!(File.read!(pack_file_p)) + {pack_dir, pack_file_p, full_pack} = pack_info(pack_name) with {_, true} <- {:has_shortcode, Map.has_key?(full_pack["files"], shortcode)}, %{"new_shortcode" => new_shortcode, "new_filename" => new_filename} <- params, @@ -522,9 +534,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") new_emoji_file_path = Path.join(pack_dir, new_filename) # If the name contains directories, create them - if String.contains?(new_emoji_file_path, "/") do - File.mkdir_p!(Path.dirname(new_emoji_file_path)) - end + create_subdirs(new_emoji_file_path) # Move/Rename the old filename to a new filename # These are probably on the same filesystem, so just rename should work @@ -540,8 +550,11 @@ keeping it in cache for #{div(cache_ms, 1000)}s") end # Then, put in the new shortcode with the new path - updated_full_pack = put_in(updated_full_pack, ["files", new_shortcode], new_filename) - update_file_and_send(conn, updated_full_pack, pack_file_p) + updated_full_pack + |> put_in(["files", new_shortcode], new_filename) + |> update_pack_file(pack_file_p) + + json(conn, %{new_shortcode => new_filename}) else {:has_shortcode, _} -> conn -- cgit v1.2.3 From 342f55fb92c723acf7f53de2dae390b72051c94b Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Sat, 28 Mar 2020 13:34:32 +0300 Subject: refactor emoji api with fixes --- lib/pleroma/emoji/pack.ex | 509 +++++++++++++++++ .../controllers/emoji_api_controller.ex | 612 ++++++--------------- lib/pleroma/web/router.ex | 3 +- 3 files changed, 677 insertions(+), 447 deletions(-) create mode 100644 lib/pleroma/emoji/pack.ex (limited to 'lib') diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex new file mode 100644 index 000000000..21ed12c78 --- /dev/null +++ b/lib/pleroma/emoji/pack.ex @@ -0,0 +1,509 @@ +defmodule Pleroma.Emoji.Pack do + @derive {Jason.Encoder, only: [:files, :pack]} + defstruct files: %{}, + pack_file: nil, + path: nil, + pack: %{}, + name: nil + + @type t() :: %__MODULE__{ + files: %{String.t() => Path.t()}, + pack_file: Path.t(), + path: Path.t(), + pack: map(), + name: String.t() + } + + alias Pleroma.Emoji + + @spec emoji_path() :: Path.t() + def emoji_path do + static = Pleroma.Config.get!([:instance, :static_dir]) + Path.join(static, "emoji") + end + + @spec create(String.t()) :: :ok | {:error, File.posix()} | {:error, :empty_values} + def create(name) when byte_size(name) > 0 do + dir = Path.join(emoji_path(), name) + + with :ok <- File.mkdir(dir) do + %__MODULE__{ + pack_file: Path.join(dir, "pack.json") + } + |> save_pack() + end + end + + def create(_), do: {:error, :empty_values} + + @spec show(String.t()) :: {:ok, t()} | {:loaded, nil} | {:error, :empty_values} + def show(name) when byte_size(name) > 0 do + with {_, %__MODULE__{} = pack} <- {:loaded, load_pack(name)}, + {_, pack} <- validate_pack(pack) do + {:ok, pack} + end + end + + def show(_), do: {:error, :empty_values} + + @spec delete(String.t()) :: + {:ok, [binary()]} | {:error, File.posix(), binary()} | {:error, :empty_values} + def delete(name) when byte_size(name) > 0 do + emoji_path() + |> Path.join(name) + |> File.rm_rf() + end + + def delete(_), do: {:error, :empty_values} + + @spec add_file(String.t(), String.t(), Path.t(), Plug.Upload.t() | String.t()) :: + {:ok, t()} | {:error, File.posix()} | {:error, :empty_values} + def add_file(name, shortcode, filename, file) + when byte_size(name) > 0 and byte_size(shortcode) > 0 and byte_size(filename) > 0 do + with {_, nil} <- {:exists, Emoji.get(shortcode)}, + {_, %__MODULE__{} = pack} <- {:loaded, load_pack(name)} do + file_path = Path.join(pack.path, filename) + + create_subdirs(file_path) + + case file do + %Plug.Upload{path: upload_path} -> + # Copy the uploaded file from the temporary directory + File.copy!(upload_path, file_path) + + url when is_binary(url) -> + # Download and write the file + file_contents = Tesla.get!(url).body + File.write!(file_path, file_contents) + end + + files = Map.put(pack.files, shortcode, filename) + + updated_pack = %{pack | files: files} + + case save_pack(updated_pack) do + :ok -> + Emoji.reload() + {:ok, updated_pack} + + e -> + e + end + end + end + + def add_file(_, _, _, _), do: {:error, :empty_values} + + defp create_subdirs(file_path) do + if String.contains?(file_path, "/") do + file_path + |> Path.dirname() + |> File.mkdir_p!() + end + end + + @spec remove_file(String.t(), String.t()) :: + {:ok, t()} | {:error, File.posix()} | {:error, :empty_values} + def remove_file(name, shortcode) when byte_size(name) > 0 and byte_size(shortcode) > 0 do + with {_, %__MODULE__{} = pack} <- {:loaded, load_pack(name)}, + {_, {filename, files}} when not is_nil(filename) <- + {:exists, Map.pop(pack.files, shortcode)}, + emoji <- Path.join(pack.path, filename), + {_, true} <- {:exists, File.exists?(emoji)} do + emoji_dir = Path.dirname(emoji) + + File.rm!(emoji) + + if String.contains?(filename, "/") and File.ls!(emoji_dir) == [] do + File.rmdir!(emoji_dir) + end + + updated_pack = %{pack | files: files} + + case save_pack(updated_pack) do + :ok -> + Emoji.reload() + {:ok, updated_pack} + + e -> + e + end + end + end + + def remove_file(_, _), do: {:error, :empty_values} + + @spec update_file(String.t(), String.t(), String.t(), String.t(), boolean()) :: + {:ok, t()} | {:error, File.posix()} | {:error, :empty_values} + def update_file(name, shortcode, new_shortcode, new_filename, force) + when byte_size(name) > 0 and byte_size(shortcode) > 0 and byte_size(new_shortcode) > 0 and + byte_size(new_filename) > 0 do + with {_, %__MODULE__{} = pack} <- {:loaded, load_pack(name)}, + {_, {filename, files}} when not is_nil(filename) <- + {:exists, Map.pop(pack.files, shortcode)}, + {_, true} <- {:not_used, force or is_nil(Emoji.get(new_shortcode))} do + old_path = Path.join(pack.path, filename) + old_dir = Path.dirname(old_path) + new_path = Path.join(pack.path, new_filename) + + create_subdirs(new_path) + + :ok = File.rename(old_path, new_path) + + if String.contains?(filename, "/") and File.ls!(old_dir) == [] do + File.rmdir!(old_dir) + end + + files = Map.put(files, new_shortcode, new_filename) + + updated_pack = %{pack | files: files} + + case save_pack(updated_pack) do + :ok -> + Emoji.reload() + {:ok, updated_pack} + + e -> + e + end + end + end + + def update_file(_, _, _, _, _), do: {:error, :empty_values} + + @spec import_from_filesystem() :: {:ok, [String.t()]} | {:error, atom()} + def import_from_filesystem do + emoji_path = emoji_path() + + with {:ok, %{access: :read_write}} <- File.stat(emoji_path), + {:ok, results} <- File.ls(emoji_path) do + names = + results + |> Enum.map(&Path.join(emoji_path, &1)) + |> Enum.reject(fn path -> + File.dir?(path) and File.exists?(Path.join(path, "pack.json")) + end) + |> Enum.map(&write_pack_contents/1) + |> Enum.filter(& &1) + + {:ok, names} + else + {:ok, %{access: _}} -> {:error, :not_writable} + e -> e + end + end + + defp write_pack_contents(path) do + pack = %__MODULE__{ + files: files_from_path(path), + path: path, + pack_file: Path.join(path, "pack.json") + } + + case save_pack(pack) do + :ok -> Path.basename(path) + _ -> nil + end + end + + defp files_from_path(path) do + txt_path = Path.join(path, "emoji.txt") + + if File.exists?(txt_path) do + # There's an emoji.txt file, it's likely from a pack installed by the pack manager. + # Make a pack.json file from the contents of that emoji.txt file + + # FIXME: Copy-pasted from Pleroma.Emoji/load_from_file_stream/2 + + # Create a map of shortcodes to filenames from emoji.txt + File.read!(txt_path) + |> String.split("\n") + |> Enum.map(&String.trim/1) + |> Enum.map(fn line -> + case String.split(line, ~r/,\s*/) do + # This matches both strings with and without tags + # and we don't care about tags here + [name, file | _] -> + file_dir_name = Path.dirname(file) + + file = + if String.ends_with?(path, file_dir_name) do + Path.basename(file) + else + file + end + + {name, file} + + _ -> + nil + end + end) + |> Enum.filter(& &1) + |> Enum.into(%{}) + else + # If there's no emoji.txt, assume all files + # that are of certain extensions from the config are emojis and import them all + pack_extensions = Pleroma.Config.get!([:emoji, :pack_extensions]) + Emoji.Loader.make_shortcode_to_file_map(path, pack_extensions) + end + end + + @spec list_remote_packs(String.t()) :: {:ok, map()} + def list_remote_packs(url) do + uri = + url + |> String.trim() + |> URI.parse() + + with {_, true} <- {:shareable, shareable_packs_available?(uri)} do + packs = + uri + |> URI.merge("/api/pleroma/emoji/packs") + |> to_string() + |> Tesla.get!() + |> Map.get(:body) + |> Jason.decode!() + + {:ok, packs} + end + end + + @spec list_local_packs() :: {:ok, map()} + def list_local_packs do + emoji_path = emoji_path() + + # Create the directory first if it does not exist. This is probably the first request made + # with the API so it should be sufficient + with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(emoji_path)}, + {:ls, {:ok, results}} <- {:ls, File.ls(emoji_path)} do + packs = + results + |> Enum.map(&load_pack/1) + |> Enum.filter(& &1) + |> Enum.map(&validate_pack/1) + |> Map.new() + + {:ok, packs} + end + end + + defp validate_pack(pack) do + if downloadable?(pack) do + archive = fetch_archive(pack) + archive_sha = :crypto.hash(:sha256, archive) |> Base.encode16() + + info = + pack.pack + |> Map.put("can-download", true) + |> Map.put("download-sha256", archive_sha) + + {pack.name, Map.put(pack, :pack, info)} + else + info = Map.put(pack.pack, "can-download", false) + {pack.name, Map.put(pack, :pack, info)} + end + end + + defp downloadable?(pack) do + # If the pack is set as shared, check if it can be downloaded + # That means that when asked, the pack can be packed and sent to the remote + # Otherwise, they'd have to download it from external-src + pack.pack["share-files"] && + Enum.all?(pack.files, fn {_, file} -> + File.exists?(Path.join(pack.path, file)) + end) + end + + @spec download(String.t()) :: {:ok, binary()} + def download(name) do + with {_, %__MODULE__{} = pack} <- {:exists?, load_pack(name)}, + {_, true} <- {:can_download?, downloadable?(pack)} do + {:ok, fetch_archive(pack)} + end + end + + defp fetch_archive(pack) do + hash = :crypto.hash(:md5, File.read!(pack.pack_file)) + + case Cachex.get!(:emoji_packs_cache, pack.name) do + %{hash: ^hash, pack_data: archive} -> + archive + + _ -> + create_archive_and_cache(pack, hash) + end + end + + defp create_archive_and_cache(pack, hash) do + files = ['pack.json' | Enum.map(pack.files, fn {_, file} -> to_charlist(file) end)] + + {:ok, {_, result}} = + :zip.zip('#{pack.name}.zip', files, [:memory, cwd: to_charlist(pack.path)]) + + ttl_per_file = Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file]) + overall_ttl = :timer.seconds(ttl_per_file * Enum.count(files)) + + Cachex.put!( + :emoji_packs_cache, + pack.name, + # if pack.json MD5 changes, the cache is not valid anymore + %{hash: hash, pack_data: result}, + # Add a minute to cache time for every file in the pack + ttl: overall_ttl + ) + + result + end + + @spec download_from_source(String.t(), String.t(), String.t()) :: :ok + def download_from_source(name, url, as) do + uri = + url + |> String.trim() + |> URI.parse() + + with {_, true} <- {:shareable, shareable_packs_available?(uri)} do + # TODO: why do we load all packs, if we know the name of pack we need + remote_pack = + uri + |> URI.merge("/api/pleroma/emoji/packs/#{name}") + |> to_string() + |> Tesla.get!() + |> Map.get(:body) + |> Jason.decode!() + + result = + case remote_pack["pack"] do + %{"share-files" => true, "can-download" => true, "download-sha256" => sha} -> + {:ok, + %{ + sha: sha, + url: + URI.merge(uri, "/api/pleroma/emoji/packs/#{name}/download_shared") |> to_string() + }} + + %{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) -> + {:ok, + %{ + sha: sha, + url: src, + fallback: true + }} + + _ -> + {:error, + "The pack was not set as shared and there is no fallback src to download from"} + end + + with {:ok, %{sha: sha, url: url} = pinfo} <- result, + %{body: archive} <- Tesla.get!(url), + {_, true} <- {:checksum, Base.decode16!(sha) == :crypto.hash(:sha256, archive)} do + local_name = as || name + + path = Path.join(emoji_path(), local_name) + + pack = %__MODULE__{ + name: local_name, + path: path, + files: remote_pack["files"], + pack_file: Path.join(path, "pack.json") + } + + File.mkdir_p!(pack.path) + + files = Enum.map(remote_pack["files"], fn {_, path} -> to_charlist(path) end) + # Fallback cannot contain a pack.json file + files = if pinfo[:fallback], do: files, else: ['pack.json' | files] + + {:ok, _} = :zip.unzip(archive, cwd: to_charlist(pack.path), file_list: files) + + # Fallback can't contain a pack.json file, since that would cause the fallback-src-sha256 + # in it to depend on itself + if pinfo[:fallback] do + save_pack(pack) + end + + :ok + end + end + end + + defp save_pack(pack), do: File.write(pack.pack_file, Jason.encode!(pack, pretty: true)) + + @spec save_metadata(map(), t()) :: {:ok, t()} | {:error, File.posix()} + def save_metadata(metadata, %__MODULE__{} = pack) do + pack = Map.put(pack, :pack, metadata) + + with :ok <- save_pack(pack) do + {:ok, pack} + end + end + + @spec update_metadata(String.t(), map()) :: {:ok, t()} | {:error, File.posix()} + def update_metadata(name, data) do + pack = load_pack(name) + + fb_sha_changed? = + not is_nil(data["fallback-src"]) and data["fallback-src"] != pack.pack["fallback-src"] + + with {_, true} <- {:update?, fb_sha_changed?}, + {:ok, %{body: zip}} <- Tesla.get(data["fallback-src"]), + {:ok, f_list} <- :zip.unzip(zip, [:memory]), + {_, true} <- {:has_all_files?, has_all_files?(pack.files, f_list)} do + fallback_sha = :crypto.hash(:sha256, zip) |> Base.encode16() + + data + |> Map.put("fallback-src-sha256", fallback_sha) + |> save_metadata(pack) + else + {:update?, _} -> save_metadata(data, pack) + e -> e + end + end + + # Check if all files from the pack.json are in the archive + defp has_all_files?(files, f_list) do + Enum.all?(files, fn {_, from_manifest} -> + List.keyfind(f_list, to_charlist(from_manifest), 0) + end) + end + + @spec load_pack(String.t()) :: t() | nil + def load_pack(name) do + pack_file = Path.join([emoji_path(), name, "pack.json"]) + + if File.exists?(pack_file) do + pack_file + |> File.read!() + |> from_json() + |> Map.put(:pack_file, pack_file) + |> Map.put(:path, Path.dirname(pack_file)) + |> Map.put(:name, name) + end + end + + defp from_json(json) do + map = Jason.decode!(json) + + struct(__MODULE__, %{files: map["files"], pack: map["pack"]}) + end + + defp shareable_packs_available?(uri) do + uri + |> URI.merge("/.well-known/nodeinfo") + |> to_string() + |> Tesla.get!() + |> Map.get(:body) + |> Jason.decode!() + |> Map.get("links") + |> List.last() + |> Map.get("href") + # Get the actual nodeinfo address and fetch it + |> Tesla.get!() + |> Map.get(:body) + |> Jason.decode!() + |> get_in(["metadata", "features"]) + |> Enum.member?("shareable_emoji_packs") + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index 981bac4fa..9fa857474 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -1,18 +1,15 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do use Pleroma.Web, :controller - alias Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug - alias Pleroma.Plugs.OAuthScopesPlug - - require Logger + alias Pleroma.Emoji.Pack plug( - OAuthScopesPlug, + Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["write"], admin: true} when action in [ :create, :delete, - :save_from, + :download_from, :import_from_fs, :update_file, :update_metadata @@ -21,17 +18,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do plug( :skip_plug, - [OAuthScopesPlug, ExpectPublicOrAuthenticatedCheckPlug] + [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug] when action in [:download_shared, :list_packs, :list_from] ) - defp emoji_dir_path do - Path.join( - Pleroma.Config.get!([:instance, :static_dir]), - "emoji" - ) - end - @doc """ Lists packs from the remote instance. @@ -39,17 +29,13 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do be done by the server """ def list_from(conn, %{"instance_address" => address}) do - address = String.trim(address) - - if shareable_packs_available(address) do - list_resp = - "#{address}/api/pleroma/emoji/packs" |> Tesla.get!() |> Map.get(:body) |> Jason.decode!() - - json(conn, list_resp) + with {:ok, packs} <- Pack.list_remote_packs(address) do + json(conn, packs) else - conn - |> put_status(:internal_server_error) - |> json(%{error: "The requested instance does not support sharing emoji packs"}) + {:shareable, _} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: "The requested instance does not support sharing emoji packs"}) end end @@ -60,113 +46,44 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do a map of "pack directory name" to pack.json contents. """ def list_packs(conn, _params) do - # Create the directory first if it does not exist. This is probably the first request made - # with the API so it should be sufficient - with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(emoji_dir_path())}, - {:ls, {:ok, results}} <- {:ls, File.ls(emoji_dir_path())} do - pack_infos = - results - |> Enum.filter(&has_pack_json?/1) - |> Enum.map(&load_pack/1) - # Check if all the files are in place and can be sent - |> Enum.map(&validate_pack/1) - # Transform into a map of pack-name => pack-data - |> Enum.into(%{}) - - json(conn, pack_infos) + emoji_path = + Path.join( + Pleroma.Config.get!([:instance, :static_dir]), + "emoji" + ) + + with {:ok, packs} <- Pack.list_local_packs() do + json(conn, packs) else {:create_dir, {:error, e}} -> conn |> put_status(:internal_server_error) - |> json(%{error: "Failed to create the emoji pack directory at #{emoji_dir_path()}: #{e}"}) + |> json(%{error: "Failed to create the emoji pack directory at #{emoji_path}: #{e}"}) {:ls, {:error, e}} -> conn |> put_status(:internal_server_error) |> json(%{ - error: - "Failed to get the contents of the emoji pack directory at #{emoji_dir_path()}: #{e}" + error: "Failed to get the contents of the emoji pack directory at #{emoji_path}: #{e}" }) end end - defp has_pack_json?(file) do - dir_path = Path.join(emoji_dir_path(), file) - # Filter to only use the pack.json packs - File.dir?(dir_path) and File.exists?(Path.join(dir_path, "pack.json")) - end - - defp load_pack(pack_name) do - pack_path = Path.join(emoji_dir_path(), pack_name) - pack_file = Path.join(pack_path, "pack.json") - - {pack_name, Jason.decode!(File.read!(pack_file))} - end - - defp validate_pack({name, pack}) do - pack_path = Path.join(emoji_dir_path(), name) - - if can_download?(pack, pack_path) do - archive_for_sha = make_archive(name, pack, pack_path) - archive_sha = :crypto.hash(:sha256, archive_for_sha) |> Base.encode16() + def show(conn, %{"name" => name}) do + name = String.trim(name) - pack = - pack - |> put_in(["pack", "can-download"], true) - |> put_in(["pack", "download-sha256"], archive_sha) - - {name, pack} + with {:ok, pack} <- Pack.show(name) do + json(conn, pack) else - {name, put_in(pack, ["pack", "can-download"], false)} - end - end - - defp can_download?(pack, pack_path) do - # If the pack is set as shared, check if it can be downloaded - # That means that when asked, the pack can be packed and sent to the remote - # Otherwise, they'd have to download it from external-src - pack["pack"]["share-files"] && - Enum.all?(pack["files"], fn {_, path} -> - File.exists?(Path.join(pack_path, path)) - end) - end - - defp create_archive_and_cache(name, pack, pack_dir, md5) do - files = - ['pack.json'] ++ - (pack["files"] |> Enum.map(fn {_, path} -> to_charlist(path) end)) - - {:ok, {_, zip_result}} = :zip.zip('#{name}.zip', files, [:memory, cwd: to_charlist(pack_dir)]) - - cache_seconds_per_file = Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file]) - cache_ms = :timer.seconds(cache_seconds_per_file * Enum.count(files)) - - Cachex.put!( - :emoji_packs_cache, - name, - # if pack.json MD5 changes, the cache is not valid anymore - %{pack_json_md5: md5, pack_data: zip_result}, - # Add a minute to cache time for every file in the pack - ttl: cache_ms - ) - - Logger.debug("Created an archive for the '#{name}' emoji pack, \ -keeping it in cache for #{div(cache_ms, 1000)}s") - - zip_result - end - - defp make_archive(name, pack, pack_dir) do - # Having a different pack.json md5 invalidates cache - pack_file_md5 = :crypto.hash(:md5, File.read!(Path.join(pack_dir, "pack.json"))) - - case Cachex.get!(:emoji_packs_cache, name) do - %{pack_file_md5: ^pack_file_md5, pack_data: zip_result} -> - Logger.debug("Using cache for the '#{name}' shared emoji pack") - zip_result + {:loaded, _} -> + conn + |> put_status(:not_found) + |> json(%{error: "Pack #{name} does not exist"}) - _ -> - create_archive_and_cache(name, pack, pack_dir, pack_file_md5) + {:error, :empty_values} -> + conn + |> put_status(:bad_request) + |> json(%{error: "pack name cannot be empty"}) end end @@ -175,21 +92,15 @@ keeping it in cache for #{div(cache_ms, 1000)}s") to download packs that the instance shares. """ def download_shared(conn, %{"name" => name}) do - pack_dir = Path.join(emoji_dir_path(), name) - pack_file = Path.join(pack_dir, "pack.json") - - with {_, true} <- {:exists?, File.exists?(pack_file)}, - pack = Jason.decode!(File.read!(pack_file)), - {_, true} <- {:can_download?, can_download?(pack, pack_dir)} do - zip_result = make_archive(name, pack, pack_dir) - send_download(conn, {:binary, zip_result}, filename: "#{name}.zip") + with {:ok, archive} <- Pack.download(name) do + send_download(conn, {:binary, archive}, filename: "#{name}.zip") else {:can_download?, _} -> conn |> put_status(:forbidden) |> json(%{ - error: "Pack #{name} cannot be downloaded from this instance, either pack sharing\ - was disabled for this pack or some files are missing" + error: + "Pack #{name} cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing" }) {:exists?, _} -> @@ -199,22 +110,6 @@ keeping it in cache for #{div(cache_ms, 1000)}s") end end - defp shareable_packs_available(address) do - "#{address}/.well-known/nodeinfo" - |> Tesla.get!() - |> Map.get(:body) - |> Jason.decode!() - |> Map.get("links") - |> List.last() - |> Map.get("href") - # Get the actual nodeinfo address and fetch it - |> Tesla.get!() - |> Map.get(:body) - |> Jason.decode!() - |> get_in(["metadata", "features"]) - |> Enum.member?("shareable_emoji_packs") - end - @doc """ An admin endpoint to request downloading and storing a pack named `pack_name` from the instance `instance_address`. @@ -222,74 +117,24 @@ keeping it in cache for #{div(cache_ms, 1000)}s") If the requested instance's admin chose to share the pack, it will be downloaded from that instance, otherwise it will be downloaded from the fallback source, if there is one. """ - def save_from(conn, %{"instance_address" => address, "pack_name" => name} = data) do - address = String.trim(address) - - if shareable_packs_available(address) do - full_pack = - "#{address}/api/pleroma/emoji/packs/list" - |> Tesla.get!() - |> Map.get(:body) - |> Jason.decode!() - |> Map.get(name) - - pack_info_res = - case full_pack["pack"] do - %{"share-files" => true, "can-download" => true, "download-sha256" => sha} -> - {:ok, - %{ - sha: sha, - uri: "#{address}/api/pleroma/emoji/packs/download_shared/#{name}" - }} - - %{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) -> - {:ok, - %{ - sha: sha, - uri: src, - fallback: true - }} - - _ -> - {:error, - "The pack was not set as shared and there is no fallback src to download from"} - end - - with {:ok, %{sha: sha, uri: uri} = pinfo} <- pack_info_res, - %{body: emoji_archive} <- Tesla.get!(uri), - {_, true} <- {:checksum, Base.decode16!(sha) == :crypto.hash(:sha256, emoji_archive)} do - local_name = data["as"] || name - pack_dir = Path.join(emoji_dir_path(), local_name) - File.mkdir_p!(pack_dir) - - files = Enum.map(full_pack["files"], fn {_, path} -> to_charlist(path) end) - # Fallback cannot contain a pack.json file - files = if pinfo[:fallback], do: files, else: ['pack.json'] ++ files - - {:ok, _} = :zip.unzip(emoji_archive, cwd: to_charlist(pack_dir), file_list: files) - - # Fallback can't contain a pack.json file, since that would cause the fallback-src-sha256 - # in it to depend on itself - if pinfo[:fallback] do - pack_file_path = Path.join(pack_dir, "pack.json") - - File.write!(pack_file_path, Jason.encode!(full_pack, pretty: true)) - end - - json(conn, "ok") - else - {:error, e} -> - conn |> put_status(:internal_server_error) |> json(%{error: e}) - - {:checksum, _} -> - conn - |> put_status(:internal_server_error) - |> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"}) - end + def download_from(conn, %{"instance_address" => address, "pack_name" => name} = params) do + with :ok <- Pack.download_from_source(name, address, params["as"]) do + json(conn, "ok") else - conn - |> put_status(:internal_server_error) - |> json(%{error: "The requested instance does not support sharing emoji packs"}) + {:shareable, _} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: "The requested instance does not support sharing emoji packs"}) + + {:checksum, _} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"}) + + {:error, e} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: e}) end end @@ -297,23 +142,27 @@ keeping it in cache for #{div(cache_ms, 1000)}s") Creates an empty pack named `name` which then can be updated via the admin UI. """ def create(conn, %{"name" => name}) do - pack_dir = Path.join(emoji_dir_path(), name) + name = String.trim(name) - if not File.exists?(pack_dir) do - File.mkdir_p!(pack_dir) - - pack_file_p = Path.join(pack_dir, "pack.json") + with :ok <- Pack.create(name) do + json(conn, "ok") + else + {:error, :eexist} -> + conn + |> put_status(:conflict) + |> json(%{error: "A pack named \"#{name}\" already exists"}) - File.write!( - pack_file_p, - Jason.encode!(%{pack: %{}, files: %{}}, pretty: true) - ) + {:error, :empty_values} -> + conn + |> put_status(:bad_request) + |> json(%{error: "pack name cannot be empty"}) - conn |> json("ok") - else - conn - |> put_status(:conflict) - |> json(%{error: "A pack named \"#{name}\" already exists"}) + {:error, _} -> + render_error( + conn, + :internal_server_error, + "Unexpected error occurred while creating pack." + ) end end @@ -321,11 +170,20 @@ keeping it in cache for #{div(cache_ms, 1000)}s") Deletes the pack `name` and all it's files. """ def delete(conn, %{"name" => name}) do - pack_dir = Path.join(emoji_dir_path(), name) + name = String.trim(name) + + with {:ok, deleted} when deleted != [] <- Pack.delete(name) do + json(conn, "ok") + else + {:ok, []} -> + conn + |> put_status(:not_found) + |> json(%{error: "Pack #{name} does not exist"}) - case File.rm_rf(pack_dir) do - {:ok, _} -> - conn |> json("ok") + {:error, :empty_values} -> + conn + |> put_status(:bad_request) + |> json(%{error: "pack name cannot be empty"}) {:error, _, _} -> conn @@ -340,82 +198,23 @@ keeping it in cache for #{div(cache_ms, 1000)}s") `new_data` is the new metadata for the pack, that will replace the old metadata. """ def update_metadata(conn, %{"pack_name" => name, "new_data" => new_data}) do - pack_file_p = Path.join([emoji_dir_path(), name, "pack.json"]) - - full_pack = Jason.decode!(File.read!(pack_file_p)) - - # The new fallback-src is in the new data and it's not the same as it was in the old data - should_update_fb_sha = - not is_nil(new_data["fallback-src"]) and - new_data["fallback-src"] != full_pack["pack"]["fallback-src"] - - with {_, true} <- {:should_update?, should_update_fb_sha}, - %{body: pack_arch} <- Tesla.get!(new_data["fallback-src"]), - {:ok, flist} <- :zip.unzip(pack_arch, [:memory]), - {_, true} <- {:has_all_files?, has_all_files?(full_pack, flist)} do - fallback_sha = :crypto.hash(:sha256, pack_arch) |> Base.encode16() - - new_data = Map.put(new_data, "fallback-src-sha256", fallback_sha) - update_metadata_and_send(conn, full_pack, new_data, pack_file_p) + with {:ok, pack} <- Pack.update_metadata(name, new_data) do + json(conn, pack.pack) else - {:should_update?, _} -> - update_metadata_and_send(conn, full_pack, new_data, pack_file_p) - {:has_all_files?, _} -> conn |> put_status(:bad_request) |> json(%{error: "The fallback archive does not have all files specified in pack.json"}) - end - end - - # Check if all files from the pack.json are in the archive - defp has_all_files?(%{"files" => files}, flist) do - Enum.all?(files, fn {_, from_manifest} -> - Enum.find(flist, fn {from_archive, _} -> - to_string(from_archive) == from_manifest - end) - end) - end - - defp update_metadata_and_send(conn, full_pack, new_data, pack_file_p) do - full_pack = Map.put(full_pack, "pack", new_data) - File.write!(pack_file_p, Jason.encode!(full_pack, pretty: true)) - - # Send new data back with fallback sha filled - json(conn, new_data) - end - - defp get_filename(%Plug.Upload{filename: filename}), do: filename - defp get_filename(url) when is_binary(url), do: Path.basename(url) - - defp empty?(str), do: String.trim(str) == "" - - defp update_pack_file(updated_full_pack, pack_file_p) do - content = Jason.encode!(updated_full_pack, pretty: true) - - File.write!(pack_file_p, content) - end - defp create_subdirs(file_path) do - if String.contains?(file_path, "/") do - file_path - |> Path.dirname() - |> File.mkdir_p!() + {:error, _} -> + render_error( + conn, + :internal_server_error, + "Unexpected error occurred while updating pack metadata." + ) end end - defp pack_info(pack_name) do - dir = Path.join(emoji_dir_path(), pack_name) - json_path = Path.join(dir, "pack.json") - - json = - json_path - |> File.read!() - |> Jason.decode!() - - {dir, json_path, json} - end - @doc """ Updates a file in a pack. @@ -436,50 +235,33 @@ keeping it in cache for #{div(cache_ms, 1000)}s") conn, %{"pack_name" => pack_name, "action" => "add"} = params ) do - shortcode = - if params["shortcode"] do - params["shortcode"] - else - filename = get_filename(params["file"]) - Path.basename(filename, Path.extname(filename)) - end - - {pack_dir, pack_file_p, full_pack} = pack_info(pack_name) - - with {_, false} <- {:has_shortcode, Map.has_key?(full_pack["files"], shortcode)}, - filename <- params["filename"] || get_filename(params["file"]), - false <- empty?(shortcode), - false <- empty?(filename), - file_path <- Path.join(pack_dir, filename) do - # If the name contains directories, create them - create_subdirs(file_path) - - case params["file"] do - %Plug.Upload{path: upload_path} -> - # Copy the uploaded file from the temporary directory - File.copy!(upload_path, file_path) - - url when is_binary(url) -> - # Download and write the file - file_contents = Tesla.get!(url).body - File.write!(file_path, file_contents) - end - - full_pack - |> put_in(["files", shortcode], filename) - |> update_pack_file(pack_file_p) - - json(conn, %{shortcode => filename}) + filename = params["filename"] || get_filename(params["file"]) + shortcode = params["shortcode"] || Path.basename(filename, Path.extname(filename)) + + with {:ok, pack} <- Pack.add_file(pack_name, shortcode, filename, params["file"]) do + json(conn, pack.files) else - {:has_shortcode, _} -> + {:exists, _} -> conn |> put_status(:conflict) |> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"}) - true -> + {:loaded, _} -> + conn + |> put_status(:bad_request) + |> json(%{error: "pack \"#{pack_name}\" is not found"}) + + {:error, :empty_values} -> conn |> put_status(:bad_request) - |> json(%{error: "shortcode or filename cannot be empty"}) + |> json(%{error: "pack name, shortcode or filename cannot be empty"}) + + {:error, _} -> + render_error( + conn, + :internal_server_error, + "Unexpected error occurred while adding file to pack." + ) end end @@ -489,87 +271,74 @@ keeping it in cache for #{div(cache_ms, 1000)}s") "action" => "remove", "shortcode" => shortcode }) do - {pack_dir, pack_file_p, full_pack} = pack_info(pack_name) - - if Map.has_key?(full_pack["files"], shortcode) do - {emoji_file_path, updated_full_pack} = pop_in(full_pack, ["files", shortcode]) - - emoji_file_path = Path.join(pack_dir, emoji_file_path) - - # Delete the emoji file - File.rm!(emoji_file_path) + with {:ok, pack} <- Pack.remove_file(pack_name, shortcode) do + json(conn, pack.files) + else + {:exists, _} -> + conn + |> put_status(:bad_request) + |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) - # If the old directory has no more files, remove it - if String.contains?(emoji_file_path, "/") do - dir = Path.dirname(emoji_file_path) + {:loaded, _} -> + conn + |> put_status(:bad_request) + |> json(%{error: "pack \"#{pack_name}\" is not found"}) - if Enum.empty?(File.ls!(dir)) do - File.rmdir!(dir) - end - end + {:error, :empty_values} -> + conn + |> put_status(:bad_request) + |> json(%{error: "pack name or shortcode cannot be empty"}) - update_pack_file(updated_full_pack, pack_file_p) - json(conn, %{shortcode => full_pack["files"][shortcode]}) - else - conn - |> put_status(:bad_request) - |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) + {:error, _} -> + render_error( + conn, + :internal_server_error, + "Unexpected error occurred while removing file from pack." + ) end end # Update def update_file( conn, - %{"pack_name" => pack_name, "action" => "update", "shortcode" => shortcode} = params + %{"pack_name" => name, "action" => "update", "shortcode" => shortcode} = params ) do - {pack_dir, pack_file_p, full_pack} = pack_info(pack_name) - - with {_, true} <- {:has_shortcode, Map.has_key?(full_pack["files"], shortcode)}, - %{"new_shortcode" => new_shortcode, "new_filename" => new_filename} <- params, - false <- empty?(new_shortcode), - false <- empty?(new_filename) do - # First, remove the old shortcode, saving the old path - {old_emoji_file_path, updated_full_pack} = pop_in(full_pack, ["files", shortcode]) - old_emoji_file_path = Path.join(pack_dir, old_emoji_file_path) - new_emoji_file_path = Path.join(pack_dir, new_filename) - - # If the name contains directories, create them - create_subdirs(new_emoji_file_path) - - # Move/Rename the old filename to a new filename - # These are probably on the same filesystem, so just rename should work - :ok = File.rename(old_emoji_file_path, new_emoji_file_path) - - # If the old directory has no more files, remove it - if String.contains?(old_emoji_file_path, "/") do - dir = Path.dirname(old_emoji_file_path) - - if Enum.empty?(File.ls!(dir)) do - File.rmdir!(dir) - end - end - - # Then, put in the new shortcode with the new path - updated_full_pack - |> put_in(["files", new_shortcode], new_filename) - |> update_pack_file(pack_file_p) - - json(conn, %{new_shortcode => new_filename}) + new_shortcode = params["new_shortcode"] + new_filename = params["new_filename"] + force = params["force"] == true + + with {:ok, pack} <- Pack.update_file(name, shortcode, new_shortcode, new_filename, force) do + json(conn, pack.files) else - {:has_shortcode, _} -> + {:exists, _} -> conn |> put_status(:bad_request) |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) - true -> + {:not_used, _} -> + conn + |> put_status(:conflict) + |> json(%{ + error: + "New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option" + }) + + {:loaded, _} -> conn |> put_status(:bad_request) - |> json(%{error: "new_shortcode or new_filename cannot be empty"}) + |> json(%{error: "pack \"#{name}\" is not found"}) - _ -> + {:error, :empty_values} -> conn |> put_status(:bad_request) - |> json(%{error: "new_shortcode or new_file were not specified"}) + |> json(%{error: "new_shortcode or new_filename cannot be empty"}) + + {:error, _} -> + render_error( + conn, + :internal_server_error, + "Unexpected error occurred while updating file in pack." + ) end end @@ -589,23 +358,12 @@ keeping it in cache for #{div(cache_ms, 1000)}s") neither, all the files with specific configured extenstions will be assumed to be emojis and stored in the new `pack.json` file. """ + def import_from_fs(conn, _params) do - emoji_path = emoji_dir_path() - - with {:ok, %{access: :read_write}} <- File.stat(emoji_path), - {:ok, results} <- File.ls(emoji_path) do - imported_pack_names = - results - |> Enum.filter(fn file -> - dir_path = Path.join(emoji_path, file) - # Find the directories that do NOT have pack.json - File.dir?(dir_path) and not File.exists?(Path.join(dir_path, "pack.json")) - end) - |> Enum.map(&write_pack_json_contents/1) - - json(conn, imported_pack_names) + with {:ok, names} <- Pack.import_from_filesystem() do + json(conn, names) else - {:ok, %{access: _}} -> + {:error, :not_writable} -> conn |> put_status(:internal_server_error) |> json(%{error: "Error: emoji pack directory must be writable"}) @@ -617,44 +375,6 @@ keeping it in cache for #{div(cache_ms, 1000)}s") end end - defp write_pack_json_contents(dir) do - dir_path = Path.join(emoji_dir_path(), dir) - emoji_txt_path = Path.join(dir_path, "emoji.txt") - - files_for_pack = files_for_pack(emoji_txt_path, dir_path) - pack_json_contents = Jason.encode!(%{pack: %{}, files: files_for_pack}) - - File.write!(Path.join(dir_path, "pack.json"), pack_json_contents) - - dir - end - - defp files_for_pack(emoji_txt_path, dir_path) do - if File.exists?(emoji_txt_path) do - # There's an emoji.txt file, it's likely from a pack installed by the pack manager. - # Make a pack.json file from the contents of that emoji.txt fileh - - # FIXME: Copy-pasted from Pleroma.Emoji/load_from_file_stream/2 - - # Create a map of shortcodes to filenames from emoji.txt - File.read!(emoji_txt_path) - |> String.split("\n") - |> Enum.map(&String.trim/1) - |> Enum.map(fn line -> - case String.split(line, ~r/,\s*/) do - # This matches both strings with and without tags - # and we don't care about tags here - [name, file | _] -> {name, file} - _ -> nil - end - end) - |> Enum.filter(fn x -> not is_nil(x) end) - |> Enum.into(%{}) - else - # If there's no emoji.txt, assume all files - # that are of certain extensions from the config are emojis and import them all - pack_extensions = Pleroma.Config.get!([:emoji, :pack_extensions]) - Pleroma.Emoji.Loader.make_shortcode_to_file_map(dir_path, pack_extensions) - end - end + defp get_filename(%Plug.Upload{filename: filename}), do: filename + defp get_filename(url) when is_binary(url), do: Path.basename(url) end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index becce3098..0fcb517cf 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -221,12 +221,13 @@ defmodule Pleroma.Web.Router do delete("/:name", EmojiAPIController, :delete) # Note: /download_from downloads and saves to instance, not to requester - post("/download_from", EmojiAPIController, :save_from) + post("/download_from", EmojiAPIController, :download_from) end # Pack info / downloading scope "/packs" do get("/", EmojiAPIController, :list_packs) + get("/:name", EmojiAPIController, :show) get("/:name/download_shared/", EmojiAPIController, :download_shared) get("/list_from", EmojiAPIController, :list_from) -- cgit v1.2.3 From ddb757f7434c7216eec1b6ba4fa8b0b7a54157c1 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Sat, 28 Mar 2020 21:15:14 +0300 Subject: emoji api packs changes in routes with docs update --- lib/pleroma/emoji/pack.ex | 26 ++-- .../controllers/emoji_api_controller.ex | 158 ++++++--------------- lib/pleroma/web/router.ex | 23 ++- 3 files changed, 64 insertions(+), 143 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index 21ed12c78..eb50e52fa 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -102,9 +102,9 @@ defmodule Pleroma.Emoji.Pack do end end - @spec remove_file(String.t(), String.t()) :: + @spec delete_file(String.t(), String.t()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values} - def remove_file(name, shortcode) when byte_size(name) > 0 and byte_size(shortcode) > 0 do + def delete_file(name, shortcode) when byte_size(name) > 0 and byte_size(shortcode) > 0 do with {_, %__MODULE__{} = pack} <- {:loaded, load_pack(name)}, {_, {filename, files}} when not is_nil(filename) <- {:exists, Map.pop(pack.files, shortcode)}, @@ -131,7 +131,7 @@ defmodule Pleroma.Emoji.Pack do end end - def remove_file(_, _), do: {:error, :empty_values} + def delete_file(_, _), do: {:error, :empty_values} @spec update_file(String.t(), String.t(), String.t(), String.t(), boolean()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values} @@ -249,8 +249,8 @@ defmodule Pleroma.Emoji.Pack do end end - @spec list_remote_packs(String.t()) :: {:ok, map()} - def list_remote_packs(url) do + @spec list_remote(String.t()) :: {:ok, map()} + def list_remote(url) do uri = url |> String.trim() @@ -269,8 +269,8 @@ defmodule Pleroma.Emoji.Pack do end end - @spec list_local_packs() :: {:ok, map()} - def list_local_packs do + @spec list_local() :: {:ok, map()} + def list_local do emoji_path = emoji_path() # Create the directory first if it does not exist. This is probably the first request made @@ -315,8 +315,8 @@ defmodule Pleroma.Emoji.Pack do end) end - @spec download(String.t()) :: {:ok, binary()} - def download(name) do + @spec get_archive(String.t()) :: {:ok, binary()} + def get_archive(name) do with {_, %__MODULE__{} = pack} <- {:exists?, load_pack(name)}, {_, true} <- {:can_download?, downloadable?(pack)} do {:ok, fetch_archive(pack)} @@ -356,15 +356,14 @@ defmodule Pleroma.Emoji.Pack do result end - @spec download_from_source(String.t(), String.t(), String.t()) :: :ok - def download_from_source(name, url, as) do + @spec download(String.t(), String.t(), String.t()) :: :ok + def download(name, url, as) do uri = url |> String.trim() |> URI.parse() with {_, true} <- {:shareable, shareable_packs_available?(uri)} do - # TODO: why do we load all packs, if we know the name of pack we need remote_pack = uri |> URI.merge("/api/pleroma/emoji/packs/#{name}") @@ -379,8 +378,7 @@ defmodule Pleroma.Emoji.Pack do {:ok, %{ sha: sha, - url: - URI.merge(uri, "/api/pleroma/emoji/packs/#{name}/download_shared") |> to_string() + url: URI.merge(uri, "/api/pleroma/emoji/packs/#{name}/archive") |> to_string() }} %{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) -> diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index 9fa857474..83a7f03e8 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -7,12 +7,15 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["write"], admin: true} when action in [ + :import, + :remote, + :download, :create, + :update, :delete, - :download_from, - :import_from_fs, + :add_file, :update_file, - :update_metadata + :delete_file ] ) @@ -22,14 +25,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do when action in [:download_shared, :list_packs, :list_from] ) - @doc """ - Lists packs from the remote instance. - - Since JS cannot ask remote instances for their packs due to CPS, it has to - be done by the server - """ - def list_from(conn, %{"instance_address" => address}) do - with {:ok, packs} <- Pack.list_remote_packs(address) do + def remote(conn, %{"url" => url}) do + with {:ok, packs} <- Pack.list_remote(url) do json(conn, packs) else {:shareable, _} -> @@ -39,20 +36,14 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do end end - @doc """ - Lists the packs available on the instance as JSON. - - The information is public and does not require authentication. The format is - a map of "pack directory name" to pack.json contents. - """ - def list_packs(conn, _params) do + def list(conn, _params) do emoji_path = Path.join( Pleroma.Config.get!([:instance, :static_dir]), "emoji" ) - with {:ok, packs} <- Pack.list_local_packs() do + with {:ok, packs} <- Pack.list_local() do json(conn, packs) else {:create_dir, {:error, e}} -> @@ -87,12 +78,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do end end - @doc """ - An endpoint for other instances (via admin UI) or users (via browser) - to download packs that the instance shares. - """ - def download_shared(conn, %{"name" => name}) do - with {:ok, archive} <- Pack.download(name) do + def archive(conn, %{"name" => name}) do + with {:ok, archive} <- Pack.get_archive(name) do send_download(conn, {:binary, archive}, filename: "#{name}.zip") else {:can_download?, _} -> @@ -110,15 +97,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do end end - @doc """ - An admin endpoint to request downloading and storing a pack named `pack_name` from the instance - `instance_address`. - - If the requested instance's admin chose to share the pack, it will be downloaded - from that instance, otherwise it will be downloaded from the fallback source, if there is one. - """ - def download_from(conn, %{"instance_address" => address, "pack_name" => name} = params) do - with :ok <- Pack.download_from_source(name, address, params["as"]) do + def download(conn, %{"url" => url, "name" => name} = params) do + with :ok <- Pack.download(name, url, params["as"]) do json(conn, "ok") else {:shareable, _} -> @@ -138,9 +118,6 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do end end - @doc """ - Creates an empty pack named `name` which then can be updated via the admin UI. - """ def create(conn, %{"name" => name}) do name = String.trim(name) @@ -166,9 +143,6 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do end end - @doc """ - Deletes the pack `name` and all it's files. - """ def delete(conn, %{"name" => name}) do name = String.trim(name) @@ -192,13 +166,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do end end - @doc """ - An endpoint to update `pack_names`'s metadata. - - `new_data` is the new metadata for the pack, that will replace the old metadata. - """ - def update_metadata(conn, %{"pack_name" => name, "new_data" => new_data}) do - with {:ok, pack} <- Pack.update_metadata(name, new_data) do + def update(conn, %{"name" => name, "metadata" => metadata}) do + with {:ok, pack} <- Pack.update_metadata(name, metadata) do json(conn, pack.pack) else {:has_all_files?, _} -> @@ -215,30 +184,11 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do end end - @doc """ - Updates a file in a pack. - - Updating can mean three things: - - - `add` adds an emoji named `shortcode` to the pack `pack_name`, - that means that the emoji file needs to be uploaded with the request - (thus requiring it to be a multipart request) and be named `file`. - There can also be an optional `filename` that will be the new emoji file name - (if it's not there, the name will be taken from the uploaded file). - - `update` changes emoji shortcode (from `shortcode` to `new_shortcode` or moves the file - (from the current filename to `new_filename`) - - `remove` removes the emoji named `shortcode` and it's associated file - """ - - # Add - def update_file( - conn, - %{"pack_name" => pack_name, "action" => "add"} = params - ) do + def add_file(conn, %{"name" => name} = params) do filename = params["filename"] || get_filename(params["file"]) shortcode = params["shortcode"] || Path.basename(filename, Path.extname(filename)) - with {:ok, pack} <- Pack.add_file(pack_name, shortcode, filename, params["file"]) do + with {:ok, pack} <- Pack.add_file(name, shortcode, filename, params["file"]) do json(conn, pack.files) else {:exists, _} -> @@ -249,7 +199,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do {:loaded, _} -> conn |> put_status(:bad_request) - |> json(%{error: "pack \"#{pack_name}\" is not found"}) + |> json(%{error: "pack \"#{name}\" is not found"}) {:error, :empty_values} -> conn @@ -265,13 +215,12 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do end end - # Remove - def update_file(conn, %{ - "pack_name" => pack_name, - "action" => "remove", - "shortcode" => shortcode - }) do - with {:ok, pack} <- Pack.remove_file(pack_name, shortcode) do + def update_file(conn, %{"name" => name, "shortcode" => shortcode} = params) do + new_shortcode = params["new_shortcode"] + new_filename = params["new_filename"] + force = params["force"] == true + + with {:ok, pack} <- Pack.update_file(name, shortcode, new_shortcode, new_filename, force) do json(conn, pack.files) else {:exists, _} -> @@ -279,35 +228,35 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do |> put_status(:bad_request) |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) + {:not_used, _} -> + conn + |> put_status(:conflict) + |> json(%{ + error: + "New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option" + }) + {:loaded, _} -> conn |> put_status(:bad_request) - |> json(%{error: "pack \"#{pack_name}\" is not found"}) + |> json(%{error: "pack \"#{name}\" is not found"}) {:error, :empty_values} -> conn |> put_status(:bad_request) - |> json(%{error: "pack name or shortcode cannot be empty"}) + |> json(%{error: "new_shortcode or new_filename cannot be empty"}) {:error, _} -> render_error( conn, :internal_server_error, - "Unexpected error occurred while removing file from pack." + "Unexpected error occurred while updating file in pack." ) end end - # Update - def update_file( - conn, - %{"pack_name" => name, "action" => "update", "shortcode" => shortcode} = params - ) do - new_shortcode = params["new_shortcode"] - new_filename = params["new_filename"] - force = params["force"] == true - - with {:ok, pack} <- Pack.update_file(name, shortcode, new_shortcode, new_filename, force) do + def delete_file(conn, %{"name" => name, "shortcode" => shortcode}) do + with {:ok, pack} <- Pack.delete_file(name, shortcode) do json(conn, pack.files) else {:exists, _} -> @@ -315,14 +264,6 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do |> put_status(:bad_request) |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) - {:not_used, _} -> - conn - |> put_status(:conflict) - |> json(%{ - error: - "New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option" - }) - {:loaded, _} -> conn |> put_status(:bad_request) @@ -331,35 +272,18 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do {:error, :empty_values} -> conn |> put_status(:bad_request) - |> json(%{error: "new_shortcode or new_filename cannot be empty"}) + |> json(%{error: "pack name or shortcode cannot be empty"}) {:error, _} -> render_error( conn, :internal_server_error, - "Unexpected error occurred while updating file in pack." + "Unexpected error occurred while removing file from pack." ) end end - def update_file(conn, %{"action" => action}) do - conn - |> put_status(:bad_request) - |> json(%{error: "Unknown action: #{action}"}) - end - - @doc """ - Imports emoji from the filesystem. - - Importing means checking all the directories in the - `$instance_static/emoji/` for directories which do not have - `pack.json`. If one has an emoji.txt file, that file will be used - to create a `pack.json` file with it's contents. If the directory has - neither, all the files with specific configured extenstions will be - assumed to be emojis and stored in the new `pack.json` file. - """ - - def import_from_fs(conn, _params) do + def import_from_filesystem(conn, _params) do with {:ok, names} <- Pack.import_from_filesystem() do json(conn, names) else diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 0fcb517cf..a7e1f2f57 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -214,25 +214,24 @@ defmodule Pleroma.Web.Router do scope "/packs" do pipe_through(:admin_api) - post("/import_from_fs", EmojiAPIController, :import_from_fs) - post("/:pack_name/update_file", EmojiAPIController, :update_file) - post("/:pack_name/update_metadata", EmojiAPIController, :update_metadata) - put("/:name", EmojiAPIController, :create) + get("/import", EmojiAPIController, :import_from_filesystem) + get("/remote", EmojiAPIController, :remote) + post("/download", EmojiAPIController, :download) + + post("/:name", EmojiAPIController, :create) + patch("/:name", EmojiAPIController, :update) delete("/:name", EmojiAPIController, :delete) - # Note: /download_from downloads and saves to instance, not to requester - post("/download_from", EmojiAPIController, :download_from) + post("/:name/files", EmojiAPIController, :add_file) + patch("/:name/files", EmojiAPIController, :update_file) + delete("/:name/files", EmojiAPIController, :delete_file) end # Pack info / downloading scope "/packs" do - get("/", EmojiAPIController, :list_packs) + get("/", EmojiAPIController, :list) get("/:name", EmojiAPIController, :show) - get("/:name/download_shared/", EmojiAPIController, :download_shared) - get("/list_from", EmojiAPIController, :list_from) - - # Deprecated: POST /api/pleroma/emoji/packs/list_from (use GET instead) - post("/list_from", EmojiAPIController, :list_from) + get("/:name/archive", EmojiAPIController, :archive) end end -- cgit v1.2.3 From 36abeedf9fdd5f90c2c03a4366f8d2cc563f9386 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Mon, 30 Mar 2020 09:09:27 +0300 Subject: error rename --- lib/pleroma/emoji/pack.ex | 2 +- lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index eb50e52fa..242344374 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -188,7 +188,7 @@ defmodule Pleroma.Emoji.Pack do {:ok, names} else - {:ok, %{access: _}} -> {:error, :not_writable} + {:ok, %{access: _}} -> {:error, :no_read_write} e -> e end end diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index 83a7f03e8..7af9d38a1 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -287,7 +287,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do with {:ok, names} <- Pack.import_from_filesystem() do json(conn, names) else - {:error, :not_writable} -> + {:error, :no_read_write} -> conn |> put_status(:internal_server_error) |> json(%{error: "Error: emoji pack directory must be writable"}) -- cgit v1.2.3 From 4a487e4d0b744edac0d83306b80e4bd0cdef5358 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 30 Apr 2020 17:50:57 +0300 Subject: fix for auth check --- lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index 7af9d38a1..d276b96a4 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["write"], admin: true} when action in [ - :import, + :import_from_filesystem, :remote, :download, :create, @@ -22,7 +22,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do plug( :skip_plug, [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug] - when action in [:download_shared, :list_packs, :list_from] + when action in [:archive, :show, :list] ) def remote(conn, %{"url" => url}) do -- cgit v1.2.3 From bef34568f0d005baabca266b99ac0f6e620e6899 Mon Sep 17 00:00:00 2001 From: eugenijm Date: Thu, 30 Apr 2020 15:02:35 +0300 Subject: Dismiss the follow request notification on rejection --- lib/pleroma/notification.ex | 10 ++++++++++ lib/pleroma/web/common_api/common_api.ex | 2 ++ 2 files changed, 12 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index aaa675253..9a109dfab 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -261,6 +261,16 @@ defmodule Pleroma.Notification do |> Repo.delete_all() end + def dismiss(%Pleroma.Activity{} = activity) do + Notification + |> where([n], n.activity_id == ^activity.id) + |> Repo.delete_all() + |> case do + {_, notifications} -> {:ok, notifications} + _ -> {:error, "Cannot dismiss notification"} + end + end + def dismiss(%{id: user_id} = _user, id) do notification = Repo.get(Notification, id) diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index d1efe0c36..4112e441a 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.ActivityExpiration alias Pleroma.Conversation.Participation alias Pleroma.FollowingRelationship + alias Pleroma.Notification alias Pleroma.Object alias Pleroma.ThreadMute alias Pleroma.User @@ -61,6 +62,7 @@ defmodule Pleroma.Web.CommonAPI do with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"), {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject), + {:ok, _notifications} <- Notification.dismiss(follow_activity), {:ok, _activity} <- ActivityPub.reject(%{ to: [follower.ap_id], -- cgit v1.2.3 From 92efb888c7b25692af205b1a4dbce0ae689c439b Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Fri, 1 May 2020 09:51:41 +0300 Subject: Made follow request notifications non-optional (removed config switch). --- lib/pleroma/notification.ex | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 9a109dfab..98289af08 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -293,17 +293,8 @@ defmodule Pleroma.Notification do end end - def create_notifications(%Activity{data: %{"type" => "Follow"}} = activity) do - if Pleroma.Config.get([:notifications, :enable_follow_request_notifications]) || - Activity.follow_accepted?(activity) do - do_create_notifications(activity) - else - {:ok, []} - end - end - def create_notifications(%Activity{data: %{"type" => type}} = activity) - when type in ["Like", "Announce", "Move", "EmojiReact"] do + when type in ["Follow", "Like", "Announce", "Move", "EmojiReact"] do do_create_notifications(activity) end -- cgit v1.2.3 From a912f72a3674f80fe665db466295192b4dab82a9 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 1 May 2020 15:54:38 +0200 Subject: Signature: Handle non-ap ids in key ids. Mastodon and Gab sometimes send the format `acct:name@server`. --- lib/pleroma/signature.ex | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex index 6b0b2c969..d01728361 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Signature do alias Pleroma.Keys alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.ObjectValidators.Types def key_id_to_actor_id(key_id) do uri = @@ -21,12 +22,23 @@ defmodule Pleroma.Signature do uri end - URI.to_string(uri) + maybe_ap_id = URI.to_string(uri) + + case Types.ObjectID.cast(maybe_ap_id) do + {:ok, ap_id} -> + {:ok, ap_id} + + _ -> + case Pleroma.Web.WebFinger.finger(maybe_ap_id) do + %{"ap_id" => ap_id} -> {:ok, ap_id} + _ -> {:error, maybe_ap_id} + end + end end def fetch_public_key(conn) do with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn), - actor_id <- key_id_to_actor_id(kid), + {:ok, actor_id} <- key_id_to_actor_id(kid), {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do {:ok, public_key} else @@ -37,7 +49,7 @@ defmodule Pleroma.Signature do def refetch_public_key(conn) do with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn), - actor_id <- key_id_to_actor_id(kid), + {:ok, actor_id} <- key_id_to_actor_id(kid), {:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id), {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do {:ok, public_key} -- cgit v1.2.3 From 3453e54e6b00ca2aced07746ad4cfc22ebc404fb Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 1 May 2020 15:58:47 +0200 Subject: MappedSignatureToIdentityPlug: Fix. --- lib/pleroma/plugs/mapped_signature_to_identity_plug.ex | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex index 84b7c5d83..f44d4dee5 100644 --- a/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex +++ b/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex @@ -13,8 +13,9 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do def init(options), do: options defp key_id_from_conn(conn) do - with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn) do - Signature.key_id_to_actor_id(key_id) + with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn), + {:ok, ap_id} <- Signature.key_id_to_actor_id(key_id) do + ap_id else _ -> nil -- cgit v1.2.3 From 85105f7aaeaaa241a9d524ab27e77d6284036051 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 1 May 2020 21:33:34 +0300 Subject: OpenAPI: Remove max pagination limit from the spec In an ideal world clients wouldn't try to request more than the max hardcoded limit, but SubwayTooter does. --- lib/pleroma/web/api_spec/helpers.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex index df0804486..183df43ee 100644 --- a/lib/pleroma/web/api_spec/helpers.ex +++ b/lib/pleroma/web/api_spec/helpers.ex @@ -41,8 +41,8 @@ defmodule Pleroma.Web.ApiSpec.Helpers do Operation.parameter( :limit, :query, - %Schema{type: :integer, default: 20, maximum: 40}, - "Limit" + %Schema{type: :integer, default: 20}, + "Maximum number of items to return. Will be ignored if it's more than 40" ) ] end -- cgit v1.2.3 From c18ef452b05355cfd573e989cad776376c4b4757 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 1 May 2020 22:48:30 +0300 Subject: OpenAPI: Add `follow_request` to notification types Closes #1731 --- lib/pleroma/web/api_spec/operations/notification_operation.ex | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/notification_operation.ex b/lib/pleroma/web/api_spec/operations/notification_operation.ex index c6514f3f2..64adc5319 100644 --- a/lib/pleroma/web/api_spec/operations/notification_operation.ex +++ b/lib/pleroma/web/api_spec/operations/notification_operation.ex @@ -178,7 +178,16 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do defp notification_type do %Schema{ type: :string, - enum: ["follow", "favourite", "reblog", "mention", "poll", "pleroma:emoji_reaction", "move"], + enum: [ + "follow", + "favourite", + "reblog", + "mention", + "poll", + "pleroma:emoji_reaction", + "move", + "follow_request" + ], description: """ The type of event that resulted in the notification. -- cgit v1.2.3 From c6ddfa8f9594377e6e0b424759d1fdbda9c9a005 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 1 May 2020 21:15:43 +0200 Subject: static-fe.css: Restore and move to /priv/static/static-fe --- lib/pleroma/constants.ex | 5 +++++ lib/pleroma/plugs/instance_static.ex | 7 +++---- lib/pleroma/web/endpoint.ex | 5 +++-- lib/pleroma/web/templates/layout/static_fe.html.eex | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index 4ba39b53f..3a9eec5ea 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -20,4 +20,9 @@ defmodule Pleroma.Constants do "deleted_activity_id" ] ) + + const(static_only_files, + do: + ~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc) + ) end diff --git a/lib/pleroma/plugs/instance_static.ex b/lib/pleroma/plugs/instance_static.ex index 927fa2663..7516f75c3 100644 --- a/lib/pleroma/plugs/instance_static.ex +++ b/lib/pleroma/plugs/instance_static.ex @@ -3,6 +3,8 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Plugs.InstanceStatic do + require Pleroma.Constants + @moduledoc """ This is a shim to call `Plug.Static` but with runtime `from` configuration. @@ -21,9 +23,6 @@ defmodule Pleroma.Plugs.InstanceStatic do end end - @only ~w(index.html robots.txt static emoji packs sounds images instance favicon.png sw.js - sw-pleroma.js) - def init(opts) do opts |> Keyword.put(:from, "__unconfigured_instance_static_plug") @@ -31,7 +30,7 @@ defmodule Pleroma.Plugs.InstanceStatic do |> Plug.Static.init() end - for only <- @only do + for only <- Pleroma.Constants.static_only_files() do at = Plug.Router.Utils.split("/") def call(%{request_path: "/" <> unquote(only) <> _} = conn, opts) do diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 72cb3ee27..226d42c2c 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -5,6 +5,8 @@ defmodule Pleroma.Web.Endpoint do use Phoenix.Endpoint, otp_app: :pleroma + require Pleroma.Constants + socket("/socket", Pleroma.Web.UserSocket) plug(Pleroma.Plugs.SetLocalePlug) @@ -34,8 +36,7 @@ defmodule Pleroma.Web.Endpoint do Plug.Static, at: "/", from: :pleroma, - only: - ~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc), + only: Pleroma.Constants.static_only_files(), # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength gzip: true, cache_control_for_etags: @static_cache_control, diff --git a/lib/pleroma/web/templates/layout/static_fe.html.eex b/lib/pleroma/web/templates/layout/static_fe.html.eex index 819632cec..dc0ee2a5c 100644 --- a/lib/pleroma/web/templates/layout/static_fe.html.eex +++ b/lib/pleroma/web/templates/layout/static_fe.html.eex @@ -5,7 +5,7 @@ <%= Pleroma.Config.get([:instance, :name]) %> <%= Phoenix.HTML.raw(assigns[:meta] || "") %> - +
-- cgit v1.2.3 From 2d07ed77477ba7b62b2cfc524f91829937e2fdb3 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 2 May 2020 18:28:04 +0300 Subject: [#1732] Made AP C2S :followers and :following endpoints serve on no auth (as for related :api pipeline endpoints). --- lib/pleroma/web/activity_pub/activity_pub_controller.ex | 3 ++- lib/pleroma/web/router.ex | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index d625530ec..f607931ab 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -37,9 +37,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do [unless_func: &FederatingPlug.federating?/0] when action not in @federating_only_actions ) + # Note: :following and :followers must be served even without authentication (as via :api) plug( EnsureAuthenticatedPlug - when action in [:read_inbox, :update_outbox, :whoami, :upload_media, :following, :followers] + when action in [:read_inbox, :update_outbox, :whoami, :upload_media] ) plug( diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 83287a83d..5b00243e9 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -585,6 +585,7 @@ defmodule Pleroma.Web.Router do post("/users/:nickname/outbox", ActivityPubController, :update_outbox) post("/api/ap/upload_media", ActivityPubController, :upload_media) + # The following two are S2S as well, see `ActivityPub.fetch_follow_information_for_user/1`: get("/users/:nickname/followers", ActivityPubController, :followers) get("/users/:nickname/following", ActivityPubController, :following) end -- cgit v1.2.3 From e55876409b523d81bc19db876bc90f29ba80a47c Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 29 Apr 2020 14:26:31 +0300 Subject: Deactivate local users on deletion instead of deleting the record Prevents the possibility of re-registration, which allowed to read DMs of the deleted account. Also includes a migration that tries to find any already deleted accounts and insert skeletons for them. Closes pleroma/pleroma#1687 --- lib/pleroma/user.ex | 11 +++++++++-- .../web/pleroma_api/controllers/pleroma_api_controller.ex | 5 ++++- 2 files changed, 13 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index b451202b2..99358ddaf 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1445,8 +1445,15 @@ defmodule Pleroma.User do end) delete_user_activities(user) - invalidate_cache(user) - Repo.delete(user) + + if user.local do + user + |> change(%{deactivated: true, email: nil}) + |> update_and_set_cache() + else + invalidate_cache(user) + Repo.delete(user) + end end def perform(:deactivate_async, user, status), do: deactivate(user, status) diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index 2c1874051..1bdb3aa4d 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -61,7 +61,10 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do else users = Enum.map(user_ap_ids, &User.get_cached_by_ap_id/1) - |> Enum.filter(& &1) + |> Enum.filter(fn + %{deactivated: false} -> true + _ -> false + end) %{ name: emoji, -- cgit v1.2.3 From 66a8e1312dc82fa755a635984f89a5314917d209 Mon Sep 17 00:00:00 2001 From: eugenijm Date: Mon, 27 Apr 2020 17:41:38 +0300 Subject: Mastodon API: do not create a following relationship if the corresponding follow request doesn't exist when calling `POST /api/v1/follow_requests/:id/authorize` --- lib/pleroma/web/common_api/common_api.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 4618b4bbf..f9db97d24 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -43,8 +43,8 @@ defmodule Pleroma.Web.CommonAPI do end def accept_follow_request(follower, followed) do - with {:ok, follower} <- User.follow(follower, followed), - %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), + with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), + {:ok, follower} <- User.follow(follower, followed), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"), {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept), {:ok, _activity} <- -- cgit v1.2.3 From 370e313e2df19e4579c154388336dd5e09bff7bf Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Sat, 2 May 2020 13:28:10 -0500 Subject: Only update follower/following stats for actor types of users and bots. --- lib/pleroma/web/activity_pub/activity_pub.ex | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 1f4a09370..31304c340 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1532,9 +1532,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp maybe_update_follow_information(data) do with {:enabled, true} <- {:enabled, Config.get([:instance, :external_user_synchronization])}, - {:ok, info} <- fetch_follow_information_for_user(data) do + {:ok, info} <- fetch_follow_information_for_user(data), + {:ok, actor_type} <- Map.fetch(data, :actor_type) do info = Map.merge(data[:info] || %{}, info) - Map.put(data, :info, info) + + cond do + actor_type in ["Person", "Service"] -> Map.put(data, :info, info) + true -> data + end else {:enabled, false} -> data -- cgit v1.2.3 From ec24c70db80665aaaf4d7794ea62e9f9e6676bec Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 4 May 2020 14:22:54 +0200 Subject: ActivityPub: Don't fetch `Application` follower counts. --- lib/pleroma/web/activity_pub/activity_pub.ex | 32 +++++++++++++++++----------- 1 file changed, 20 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 31304c340..1c21d78af 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1530,26 +1530,34 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp normalize_counter(counter) when is_integer(counter), do: counter defp normalize_counter(_), do: 0 - defp maybe_update_follow_information(data) do + def maybe_update_follow_information(user_data) do with {:enabled, true} <- {:enabled, Config.get([:instance, :external_user_synchronization])}, - {:ok, info} <- fetch_follow_information_for_user(data), - {:ok, actor_type} <- Map.fetch(data, :actor_type) do - info = Map.merge(data[:info] || %{}, info) - - cond do - actor_type in ["Person", "Service"] -> Map.put(data, :info, info) - true -> data - end + {_, true} <- {:user_type_check, user_data[:type] in ["Person", "Service"]}, + {_, true} <- + {:collections_available, + !!(user_data[:following_address] && user_data[:follower_address])}, + {:ok, info} <- + fetch_follow_information_for_user(user_data) do + info = Map.merge(user_data[:info] || %{}, info) + + user_data + |> Map.put(:info, info) else + {:user_type_check, false} -> + user_data + + {:collections_available, false} -> + user_data + {:enabled, false} -> - data + user_data e -> Logger.error( - "Follower/Following counter update for #{data.ap_id} failed.\n" <> inspect(e) + "Follower/Following counter update for #{user_data.ap_id} failed.\n" <> inspect(e) ) - data + user_data end end -- cgit v1.2.3 From 13ab8defc0c5f9728c00ce49044dd60c02c7e81f Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 4 May 2020 14:34:19 +0200 Subject: Pipeline: Move transctioning to common pipeline. --- lib/pleroma/web/activity_pub/pipeline.ex | 23 +++++++++++++++++++---- lib/pleroma/web/activity_pub/side_effects.ex | 13 ++++--------- 2 files changed, 23 insertions(+), 13 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex index 7ccee54c9..d5abb7567 100644 --- a/lib/pleroma/web/activity_pub/pipeline.ex +++ b/lib/pleroma/web/activity_pub/pipeline.ex @@ -4,20 +4,33 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.Repo alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.ObjectValidator alias Pleroma.Web.ActivityPub.SideEffects alias Pleroma.Web.Federator - @spec common_pipeline(map(), keyword()) :: {:ok, Activity.t(), keyword()} | {:error, any()} + @spec common_pipeline(map(), keyword()) :: + {:ok, Activity.t() | Object.t(), keyword()} | {:error, any()} def common_pipeline(object, meta) do + case Repo.transaction(fn -> do_common_pipeline(object, meta) end) do + {:ok, value} -> + value + + {:error, e} -> + {:error, e} + end + end + + def do_common_pipeline(object, meta) do with {_, {:ok, validated_object, meta}} <- {:validate_object, ObjectValidator.validate(object, meta)}, {_, {:ok, mrfd_object}} <- {:mrf_object, MRF.filter(validated_object)}, - {_, {:ok, %Activity{} = activity, meta}} <- + {_, {:ok, activity, meta}} <- {:persist_object, ActivityPub.persist(mrfd_object, meta)}, - {_, {:ok, %Activity{} = activity, meta}} <- + {_, {:ok, activity, meta}} <- {:execute_side_effects, SideEffects.handle(activity, meta)}, {_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do {:ok, activity, meta} @@ -27,7 +40,9 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do end end - defp maybe_federate(activity, meta) do + defp maybe_federate(%Object{}, _), do: {:ok, :not_federated} + + defp maybe_federate(%Activity{} = activity, meta) do with {:ok, local} <- Keyword.fetch(meta, :local) do if local do Federator.publish(activity) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 5981e7545..6a8f1af96 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -15,17 +15,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do # - Add like to object # - Set up notification def handle(%{data: %{"type" => "Like"}} = object, meta) do - {:ok, result} = - Pleroma.Repo.transaction(fn -> - liked_object = Object.get_by_ap_id(object.data["object"]) - Utils.add_like_to_object(object, liked_object) + liked_object = Object.get_by_ap_id(object.data["object"]) + Utils.add_like_to_object(object, liked_object) - Notification.create_notifications(object) + Notification.create_notifications(object) - {:ok, object, meta} - end) - - result + {:ok, object, meta} end # Nothing to do -- cgit v1.2.3 From e03c301ebeea6687ee7f19d447232864b182d581 Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 4 May 2020 17:08:31 +0200 Subject: LikeValidator: Fix up missing recipients. --- .../object_validators/like_validator.ex | 34 ++++++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex index 49546ceaa..d9ee07995 100644 --- a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do use Ecto.Schema + alias Pleroma.Object alias Pleroma.Web.ActivityPub.ObjectValidators.Types alias Pleroma.Web.ActivityPub.Utils @@ -19,8 +20,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do field(:object, Types.ObjectID) field(:actor, Types.ObjectID) field(:context, :string) - field(:to, {:array, :string}) - field(:cc, {:array, :string}) + field(:to, {:array, :string}, default: []) + field(:cc, {:array, :string}, default: []) end def cast_and_validate(data) do @@ -31,7 +32,34 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do def cast_data(data) do %__MODULE__{} - |> cast(data, [:id, :type, :object, :actor, :context, :to, :cc]) + |> changeset(data) + end + + def changeset(struct, data) do + struct + |> cast(data, __schema__(:fields)) + |> fix_after_cast() + end + + def fix_after_cast(cng) do + cng + |> fix_recipients() + end + + def fix_recipients(cng) do + to = get_field(cng, :to) || [] + cc = get_field(cng, :cc) || [] + object = get_field(cng, :object) + + with {[], []} <- {to, cc}, + %Object{data: %{"actor" => actor}} <- Object.get_cached_by_ap_id(object), + {:ok, actor} <- Types.ObjectID.cast(actor) do + cng + |> put_change(:to, [actor]) + else + _ -> + cng + end end def validate_data(data_cng) do -- cgit v1.2.3 From 0f9bed022fab80f59353597dde82896ef954a678 Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 4 May 2020 17:18:17 +0200 Subject: LikeValidator: Fix up context. --- .../activity_pub/object_validators/like_validator.ex | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex index d9ee07995..1bce739bd 100644 --- a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex @@ -44,11 +44,25 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do def fix_after_cast(cng) do cng |> fix_recipients() + |> fix_context() + end + + def fix_context(cng) do + object = get_field(cng, :object) + + with nil <- get_field(cng, :context), + %Object{data: %{"context" => context}} <- Object.get_cached_by_ap_id(object) do + cng + |> put_change(:context, context) + else + _ -> + cng + end end def fix_recipients(cng) do - to = get_field(cng, :to) || [] - cc = get_field(cng, :cc) || [] + to = get_field(cng, :to) + cc = get_field(cng, :cc) object = get_field(cng, :object) with {[], []} <- {to, cc}, -- cgit v1.2.3 From 3559dd108518dfa740878cecdc86c0357b265857 Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 4 May 2020 17:18:38 +0200 Subject: Transmogrifier: Rely on LikeValidator. --- lib/pleroma/web/activity_pub/transmogrifier.ex | 55 ++------------------------ 1 file changed, 3 insertions(+), 52 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index c966ec960..581e7040b 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -15,7 +15,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ObjectValidator - alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility @@ -658,16 +657,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end def handle_incoming(%{"type" => "Like"} = data, _options) do - with {_, {:ok, cast_data_sym}} <- - {:casting_data, - data |> LikeValidator.cast_data() |> Ecto.Changeset.apply_action(:insert)}, - cast_data = ObjectValidator.stringify_keys(Map.from_struct(cast_data_sym)), - :ok <- ObjectValidator.fetch_actor_and_object(cast_data), - {_, {:ok, cast_data}} <- {:ensure_context_presence, ensure_context_presence(cast_data)}, - {_, {:ok, cast_data}} <- - {:ensure_recipients_presence, ensure_recipients_presence(cast_data)}, - {_, {:ok, activity, _meta}} <- - {:common_pipeline, Pipeline.common_pipeline(cast_data, local: false)} do + with :ok <- ObjectValidator.fetch_actor_and_object(data), + {:ok, activity, _meta} <- + Pipeline.common_pipeline(data, local: false) do {:ok, activity} else e -> {:error, e} @@ -1296,45 +1288,4 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def maybe_fix_user_url(data), do: data def maybe_fix_user_object(data), do: maybe_fix_user_url(data) - - defp ensure_context_presence(%{"context" => context} = data) when is_binary(context), - do: {:ok, data} - - defp ensure_context_presence(%{"object" => object} = data) when is_binary(object) do - with %{data: %{"context" => context}} when is_binary(context) <- Object.normalize(object) do - {:ok, Map.put(data, "context", context)} - else - _ -> - {:error, :no_context} - end - end - - defp ensure_context_presence(_) do - {:error, :no_context} - end - - defp ensure_recipients_presence(%{"to" => [_ | _], "cc" => [_ | _]} = data), - do: {:ok, data} - - defp ensure_recipients_presence(%{"object" => object} = data) do - case Object.normalize(object) do - %{data: %{"actor" => actor}} -> - data = - data - |> Map.put("to", [actor]) - |> Map.put("cc", data["cc"] || []) - - {:ok, data} - - nil -> - {:error, :no_object} - - _ -> - {:error, :no_actor} - end - end - - defp ensure_recipients_presence(_) do - {:error, :no_object} - end end -- cgit v1.2.3