Automatic checks of authentication / instance publicity. Definition of missing OAuth...
authorIvan Tashkinov <ivantashkinov@gmail.com>
Tue, 21 Apr 2020 13:29:19 +0000 (16:29 +0300)
committerIvan Tashkinov <ivantashkinov@gmail.com>
Tue, 21 Apr 2020 13:29:19 +0000 (16:29 +0300)
44 files changed:
docs/dev.md [new file with mode: 0644]
lib/pleroma/plugs/auth_expected_plug.ex [deleted file]
lib/pleroma/plugs/ensure_authenticated_plug.ex
lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex
lib/pleroma/plugs/expect_authenticated_check_plug.ex [new file with mode: 0644]
lib/pleroma/plugs/expect_public_or_authenticated_check_plug.ex [new file with mode: 0644]
lib/pleroma/plugs/oauth_scopes_plug.ex
lib/pleroma/web/admin_api/admin_api_controller.ex
lib/pleroma/web/fallback_redirect_controller.ex
lib/pleroma/web/masto_fe_controller.ex
lib/pleroma/web/mastodon_api/controllers/account_controller.ex
lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex
lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex
lib/pleroma/web/mastodon_api/controllers/filter_controller.ex
lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex
lib/pleroma/web/mastodon_api/controllers/list_controller.ex
lib/pleroma/web/mastodon_api/controllers/marker_controller.ex
lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
lib/pleroma/web/mastodon_api/controllers/media_controller.ex
lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
lib/pleroma/web/mastodon_api/controllers/poll_controller.ex
lib/pleroma/web/mastodon_api/controllers/report_controller.ex
lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex
lib/pleroma/web/mastodon_api/controllers/search_controller.ex
lib/pleroma/web/mastodon_api/controllers/status_controller.ex
lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex
lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
lib/pleroma/web/media_proxy/media_proxy_controller.ex
lib/pleroma/web/pleroma_api/controllers/account_controller.ex
lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex
lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex
lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex
lib/pleroma/web/router.ex
lib/pleroma/web/twitter_api/controllers/util_controller.ex
lib/pleroma/web/twitter_api/twitter_api_controller.ex
lib/pleroma/web/web.ex
test/plugs/ensure_authenticated_plug_test.exs
test/plugs/ensure_public_or_authenticated_plug_test.exs
test/plugs/oauth_scopes_plug_test.exs
test/web/activity_pub/activity_pub_controller_test.exs
test/web/pleroma_api/controllers/emoji_api_controller_test.exs
test/web/twitter_api/twitter_api_controller_test.exs

diff --git a/docs/dev.md b/docs/dev.md
new file mode 100644 (file)
index 0000000..0ecf43a
--- /dev/null
@@ -0,0 +1,33 @@
+This document contains notes and guidelines for Pleroma developers.
+
+# Authentication & Authorization
+
+## OAuth token-based authentication & authorization
+
+* Pleroma supports hierarchical OAuth scopes, just like Mastodon but with added granularity of admin scopes.
+  For a reference, see [Mastodon OAuth scopes](https://docs.joinmastodon.org/api/oauth-scopes/).
+
+* It is important to either define OAuth scope restrictions or explicitly mark OAuth scope check as skipped, for every 
+    controller action. To define scopes, call `plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: [...]})`. To explicitly set 
+    OAuth scopes check skipped, call `plug(:skip_plug, Pleroma.Plugs.OAuthScopesPlug <when ...>)`.
+
+* In controllers, `use Pleroma.Web, :controller` will result in `action/2` (see `Pleroma.Web.controller/0` for definition)
+    be called prior to actual controller action, and it'll perform security / privacy checks before passing control to
+    actual controller action. For routes with `:authenticated_api` pipeline, authentication & authorization are expected,
+    thus `OAuthScopesPlug` will be run unless explicitly skipped (also `EnsureAuthenticatedPlug` will be executed
+    immediately before action even if there was an early run to give an early error, since `OAuthScopesPlug` supports
+    `:proceed_unauthenticated` option, and other plugs may support similar options as well). For `:api` pipeline routes,
+    `EnsurePublicOrAuthenticatedPlug` will be called to ensure that the instance is not private or user is authenticated
+    (unless explicitly skipped). Such automated checks help to prevent human errors and result in higher security / privacy
+    for users.
+
+## [HTTP Basic Authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization)
+
+* With HTTP Basic Auth, OAuth scopes check is _not_ performed for any action (since password is provided during the auth,
+    requester is able to obtain a token with full permissions anyways). `Pleroma.Plugs.AuthenticationPlug` and
+    `Pleroma.Plugs.LegacyAuthenticationPlug` both call `Pleroma.Plugs.OAuthScopesPlug.skip_plug(conn)` when password
+    is provided.
+
+## Auth-related configuration, OAuth consumer mode etc.
+
+See `Authentication` section of [`docs/configuration/cheatsheet.md`](docs/configuration/cheatsheet.md#authentication).
diff --git a/lib/pleroma/plugs/auth_expected_plug.ex b/lib/pleroma/plugs/auth_expected_plug.ex
deleted file mode 100644 (file)
index f79597d..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# 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
index 054d2297f1906a31ee4810823299474c8caf5e17..9c8f5597f72764ee9dd31dc6b701a9ce21854299 100644 (file)
@@ -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].()
index d980ff13d133bf64edd0e45dfea861e26a28e502..7265bb87aaa0a158f94c9da3dc16260fd1fa4d28 100644 (file)
@@ -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 (file)
index 0000000..66b8d5d
--- /dev/null
@@ -0,0 +1,20 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# 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 (file)
index 0000000..ba0ef76
--- /dev/null
@@ -0,0 +1,21 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# 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
index 66f48c28c600f8fda50dea5c326edf47027125cc..a61582566ce6dfa6e7ae34ecc5a41f0ba4d02a78 100644 (file)
@@ -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
index 9c79310c08ef1ff1a2614bc52acbfe233bf59361..816c11e01f593ff8809811bb0fc461ff56776d9a 100644 (file)
@@ -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"))
index c13518030116ee9abe1532e2ed46c8a5013ab7c3..0d9d578fcc2895f9e0690e8f4a28abeef98e4e01 100644 (file)
@@ -4,7 +4,9 @@
 
 defmodule Fallback.RedirectController do
   use Pleroma.Web, :controller
+
   require Logger
+
   alias Pleroma.User
   alias Pleroma.Web.Metadata
 
index 557cde328f4cdd47d646d1b2f21769d34a66e8a4..9a2ec517aad01f6ffbb45ddfd3e4ccbdd1e26976 100644 (file)
@@ -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)
index e8e59ac66c06b6f0de5cd5012676785e163f8b09..9b8cc0d0dc18a0474e5c879a10905b08541b8274 100644 (file)
@@ -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
index 37b38938241109b36741cee67fb835958b01dcfa..753b3db3ef9c9cfdd93081b038cadcef9290e6ae 100644 (file)
@@ -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))
index 7c9b11bf1753354a73f3ea2f4366b77094829a7c..c446415261978ef0a2dabbdd8d921ff6699ff696 100644 (file)
@@ -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
index 84de794136fee87c2c8b73818c8b8e5e886db669..c4fa383f222df5743f36df597db1e3ae1f8c1d6f 100644 (file)
@@ -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, []))
index 7b0b937a26ea4f0290c6fd199315064542e1e495..7fd0562c98ceeee9838658063a73ecd66bddbf90 100644 (file)
@@ -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)
index 1ca86f63fb2371aba7d0af409cc48603cae8cf13..25f2269b97687a07c59b4193f59c51bc918b608c 100644 (file)
@@ -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)
index dac4daa7bc0dd0a3b67d9f224bf43434a850578e..bfe856025af0303882afbce96460a84da69fc1d1 100644 (file)
@@ -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
index 58e8a30c29f49763c43607db20420d12daf5b84a..9f9d4574ee28db18bdb197b080e30e3d8bf10738 100644 (file)
@@ -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
index ac8c18f24272892d387406aa30d3d8f24cd87a3d..f0492b1893b78302ba90c336d72ff48630667c95 100644 (file)
@@ -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
index 2b6f00952a4c99057c661d6dd334f45691c2f318..e367512201f934437d59e6dcb8a8ba9020acfac8 100644 (file)
@@ -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} <-
index 7fb536b0935e7e9c69590b3724487f66700c45ce..31140527783f6bd90c3f0ae413fe689d302c3483 100644 (file)
@@ -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
index d9f89411881c5a223486f4a50b9b554d2c6943db..af9b66eff17cf1d2f94d0d8f1af7acfc8130f8ac 100644 (file)
@@ -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),
index f5782be130f7845a533c7391bf4afe67a8e5b8bb..9fbaa7bd1681487cf691fc63919bf3b84377a427 100644 (file)
@@ -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
index e1e6bd89b9185b4d19d90bff7e59bd52bf8d685f..899b7887391cdae6ac7c563ed3602098ff1afbb0 100644 (file)
@@ -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"
index c258742dd80eacb710dac953650dbe9d73b0298c..b54c569675d9e7fe5937c4cbc80ca6d281c7a4b1 100644 (file)
@@ -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
index 397dd10e3462c7744ef6a3d78066149302577db7..eade83aafc6559ce7879b6d61c35153a286663db 100644 (file)
@@ -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
 
index 4647c1f96db01a13928019824a0a61d16319ed1a..d184ea1d025924ce9b874e3506578cdf3af7fad0 100644 (file)
@@ -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
index b3c58005eb170e6a2cbebef154eb63449ce2732f..891f924bc929d3de8852849165f15b4cfe5ec809 100644 (file)
@@ -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)
 
index 1a09ac62a0245d74b80c2a0d65405355618a5ae5..4657a4383563802f19fef4a25730ac6e121ebf82 100644 (file)
@@ -4,6 +4,7 @@
 
 defmodule Pleroma.Web.MediaProxy.MediaProxyController do
   use Pleroma.Web, :controller
+
   alias Pleroma.ReverseProxy
   alias Pleroma.Web.MediaProxy
 
index 60405fbff2c7aac9376df0a59b5283f21da87481..d6ffdcbe4923a6048397479121006d4c0f6e2d1f 100644 (file)
@@ -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)
 
index 03e95e0202fa7454705c24f2a3ec6c4ae7ec91cd..e01825b48b28b9374f8d949c749a5f7bb3621b57 100644 (file)
@@ -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
index d9c1c863622e80b41e764e81e6e53807b0dc9a0a..d4e0d8b7cc00359fb2fd75a1ecfadf23d9042e18 100644 (file)
@@ -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))
index fe1b97a208c9738b81bbc75d7e2c07cf91b5169a..7a65697e83ea0ebfa173195861b32a505b4c9ded 100644 (file)
@@ -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)
 
index 4463ec47741762244640da303617f26ed951bdc3..c81e8535ec3ea9e69996d1e5acfadfb9023460dd 100644 (file)
@@ -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
index 153802a432f42ff702b0684e87bce09ceab27d93..04c1c594178218a83bfbf1f9339903ec5b287812 100644 (file)
@@ -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
index 537f9f778aa967b7618bf96e1eb7657002d8dc16..9a4c39fa91779963139f7047366574767933a0ff 100644 (file)
@@ -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(
index 31adc28174bf1c7af9855c82f2907290d4f681c0..55228616ab7d5bcfdecf63ac68f7b3ac66d1806f 100644 (file)
@@ -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
 
index bf48ce26c8380202b9208095aed92f9d072abf7a..ec04c05f088853cefc5a8b58561a3c1be58409e4 100644 (file)
@@ -2,6 +2,11 @@
 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
 # 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
index 7f3559b837cc921d9fc4633081b0c6cd651f851e..689fe757f5d00f2d712814dcca9a53a39cfbccb9 100644 (file)
@@ -20,7 +20,7 @@ defmodule Pleroma.Plugs.EnsureAuthenticatedPlugTest do
       conn = assign(conn, :user, %User{})
       ret_conn = EnsureAuthenticatedPlug.call(conn, %{})
 
-      assert ret_conn == conn
+      refute ret_conn.halted
     end
   end
 
@@ -34,20 +34,22 @@ defmodule Pleroma.Plugs.EnsureAuthenticatedPlugTest do
 
     test "it continues if a user is assigned", %{conn: conn, true_fn: true_fn, false_fn: false_fn} do
       conn = assign(conn, :user, %User{})
-      assert EnsureAuthenticatedPlug.call(conn, if_func: true_fn) == conn
-      assert EnsureAuthenticatedPlug.call(conn, if_func: false_fn) == conn
-      assert EnsureAuthenticatedPlug.call(conn, unless_func: true_fn) == conn
-      assert EnsureAuthenticatedPlug.call(conn, unless_func: false_fn) == conn
+      refute EnsureAuthenticatedPlug.call(conn, if_func: true_fn).halted
+      refute EnsureAuthenticatedPlug.call(conn, if_func: false_fn).halted
+      refute EnsureAuthenticatedPlug.call(conn, unless_func: true_fn).halted
+      refute EnsureAuthenticatedPlug.call(conn, unless_func: false_fn).halted
     end
 
     test "it continues if a user is NOT assigned but :if_func evaluates to `false`",
          %{conn: conn, false_fn: false_fn} do
-      assert EnsureAuthenticatedPlug.call(conn, if_func: false_fn) == conn
+      ret_conn = EnsureAuthenticatedPlug.call(conn, if_func: false_fn)
+      refute ret_conn.halted
     end
 
     test "it continues if a user is NOT assigned but :unless_func evaluates to `true`",
          %{conn: conn, true_fn: true_fn} do
-      assert EnsureAuthenticatedPlug.call(conn, unless_func: true_fn) == conn
+      ret_conn = EnsureAuthenticatedPlug.call(conn, unless_func: true_fn)
+      refute ret_conn.halted
     end
 
     test "it halts if a user is NOT assigned and :if_func evaluates to `true`",
index 411252274c5f0f0a008d8604bcd9257cbde8ced8..fc2934369b573101a209c0cc7f868ed92656ddd9 100644 (file)
@@ -29,7 +29,7 @@ defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlugTest do
       conn
       |> EnsurePublicOrAuthenticatedPlug.call(%{})
 
-    assert ret_conn == conn
+    refute ret_conn.halted
   end
 
   test "it continues if a user is assigned, even if not public", %{conn: conn} do
@@ -43,6 +43,6 @@ defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlugTest do
       conn
       |> EnsurePublicOrAuthenticatedPlug.call(%{})
 
-    assert ret_conn == conn
+    refute ret_conn.halted
   end
 end
index edbc942273ef1c54a4d235141deca37ee37b7c37..884de7b4d5d37319d42c9d25cbcfd30541c1e7af 100644 (file)
@@ -5,17 +5,12 @@
 defmodule Pleroma.Plugs.OAuthScopesPlugTest do
   use Pleroma.Web.ConnCase, async: true
 
-  alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
   alias Pleroma.Plugs.OAuthScopesPlug
   alias Pleroma.Repo
 
   import Mock
   import Pleroma.Factory
 
-  setup_with_mocks([{EnsurePublicOrAuthenticatedPlug, [], [call: fn conn, _ -> conn end]}]) do
-    :ok
-  end
-
   test "is not performed if marked as skipped", %{conn: conn} do
     with_mock OAuthScopesPlug, [:passthrough], perform: &passthrough([&1, &2]) do
       conn =
@@ -60,7 +55,7 @@ defmodule Pleroma.Plugs.OAuthScopesPlugTest do
 
   describe "with `fallback: :proceed_unauthenticated` option, " do
     test "if `token.scopes` doesn't fulfill specified conditions, " <>
-           "clears :user and :token assigns and calls EnsurePublicOrAuthenticatedPlug",
+           "clears :user and :token assigns",
          %{conn: conn} do
       user = insert(:user)
       token1 = insert(:oauth_token, scopes: ["read", "write"], user: user)
@@ -79,35 +74,6 @@ defmodule Pleroma.Plugs.OAuthScopesPlugTest do
         refute ret_conn.halted
         refute ret_conn.assigns[:user]
         refute ret_conn.assigns[:token]
-
-        assert called(EnsurePublicOrAuthenticatedPlug.call(ret_conn, :_))
-      end
-    end
-
-    test "with :skip_instance_privacy_check option, " <>
-           "if `token.scopes` doesn't fulfill specified conditions, " <>
-           "clears :user and :token assigns and does NOT call EnsurePublicOrAuthenticatedPlug",
-         %{conn: conn} do
-      user = insert(:user)
-      token1 = insert(:oauth_token, scopes: ["read:statuses", "write"], user: user)
-
-      for token <- [token1, nil], op <- [:|, :&] do
-        ret_conn =
-          conn
-          |> assign(:user, user)
-          |> assign(:token, token)
-          |> OAuthScopesPlug.call(%{
-            scopes: ["read"],
-            op: op,
-            fallback: :proceed_unauthenticated,
-            skip_instance_privacy_check: true
-          })
-
-        refute ret_conn.halted
-        refute ret_conn.assigns[:user]
-        refute ret_conn.assigns[:token]
-
-        refute called(EnsurePublicOrAuthenticatedPlug.call(ret_conn, :_))
       end
     end
   end
index fbacb399335cd5bc56e4e198504175a98f8776b5..eca526604af7e970c204979180d9dc013c21054c 100644 (file)
@@ -766,7 +766,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
   end
 
   describe "POST /users/:nickname/outbox" do
-    test "it rejects posts from other users / unauuthenticated users", %{conn: conn} do
+    test "it rejects posts from other users / unauthenticated users", %{conn: conn} do
       data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
       user = insert(:user)
       other_user = insert(:user)
index 435fb65921d83b582539a56405f2209778aaf605..4246eb400046422efb4c5c5ec6466711519cdfbc 100644 (file)
@@ -38,8 +38,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
   end
 
   test "listing remote packs" do
-    admin = insert(:user, is_admin: true)
-    %{conn: conn} = oauth_access(["admin:write"], user: admin)
+    conn = build_conn()
 
     resp =
       build_conn()
@@ -76,7 +75,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
     assert Enum.find(arch, fn {n, _} -> n == 'blank.png' end)
   end
 
-  test "downloading shared & unshared packs from another instance via download_from, deleting them" do
+  test "downloading shared & unshared packs from another instance, deleting them" do
     on_exit(fn ->
       File.rm_rf!("#{@emoji_dir_path}/test_pack2")
       File.rm_rf!("#{@emoji_dir_path}/test_pack_nonshared2")
@@ -136,7 +135,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
             |> post(
               emoji_api_path(
                 conn,
-                :download_from
+                :save_from
               ),
               %{
                 instance_address: "https://old-instance",
@@ -152,7 +151,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
            |> post(
              emoji_api_path(
                conn,
-               :download_from
+               :save_from
              ),
              %{
                instance_address: "https://example.com",
@@ -179,7 +178,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
            |> post(
              emoji_api_path(
                conn,
-               :download_from
+               :save_from
              ),
              %{
                instance_address: "https://example.com",
index ab0a2c3df094cf7dc744421871ee7b0f77ab8a43..464d0ea2eed10825ed7210df2b4c323cc2ccb195 100644 (file)
@@ -19,13 +19,9 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
     end
 
     test "with credentials, without any params" do
-      %{user: current_user, conn: conn} =
-        oauth_access(["read:notifications", "write:notifications"])
+      %{conn: conn} = oauth_access(["write:notifications"])
 
-      conn =
-        conn
-        |> assign(:user, current_user)
-        |> post("/api/qvitter/statuses/notifications/read")
+      conn = post(conn, "/api/qvitter/statuses/notifications/read")
 
       assert json_response(conn, 400) == %{
                "error" => "You need to specify latest_id",