Credo fixes.
[akkoma] / lib / pleroma / web / web.ex
index 74b13f929def29a15990773044369d99f6fd8eca..bf48ce26c8380202b9208095aed92f9d072abf7a 100644 (file)
@@ -1,5 +1,5 @@
 # Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web do
@@ -23,8 +23,51 @@ defmodule Pleroma.Web do
   def controller do
     quote do
       use Phoenix.Controller, namespace: Pleroma.Web
+
       import Plug.Conn
-      import Pleroma.Web.{Gettext, Router.Helpers}
+      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
+      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
+          super(conn, params)
+        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
+          conn
+          |> render_error(
+            :forbidden,
+            "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
+          )
+          |> halt()
+        else
+          conn
+        end
+      end
     end
   end
 
@@ -37,13 +80,43 @@ defmodule Pleroma.Web do
       # Import convenience functions from controllers
       import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
 
-      import Pleroma.Web.{ErrorHelpers, Gettext, Router.Helpers}
+      import Pleroma.Web.ErrorHelpers
+      import Pleroma.Web.Gettext
+      import Pleroma.Web.Router.Helpers
+
+      require Logger
+
+      @doc "Same as `render/3` but wrapped in a rescue block"
+      def safe_render(view, template, assigns \\ %{}) do
+        Phoenix.View.render(view, template, assigns)
+      rescue
+        error ->
+          Logger.error(
+            "#{__MODULE__} failed to render #{inspect({view, template})}\n" <>
+              Exception.format(:error, error, __STACKTRACE__)
+          )
+
+          nil
+      end
+
+      @doc """
+      Same as `render_many/4` but wrapped in rescue block.
+      """
+      def safe_render_many(collection, view, template, assigns \\ %{}) do
+        Enum.map(collection, fn resource ->
+          as = Map.get(assigns, :as) || view.__resource__
+          assigns = Map.put(assigns, as, resource)
+          safe_render(view, template, assigns)
+        end)
+        |> Enum.filter(& &1)
+      end
     end
   end
 
   def router do
     quote do
       use Phoenix.Router
+      # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
       import Plug.Conn
       import Phoenix.Controller
     end
@@ -51,11 +124,41 @@ defmodule Pleroma.Web do
 
   def channel do
     quote do
+      # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
       use Phoenix.Channel
       import Pleroma.Web.Gettext
     end
   end
 
+  def plug do
+    quote do
+      alias Pleroma.Plugs.PlugHelper
+
+      @doc """
+      Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain.
+      """
+      def skip_plug(conn) do
+        PlugHelper.append_to_private_list(
+          conn,
+          PlugHelper.skipped_plugs_list_id(),
+          __MODULE__
+        )
+      end
+
+      @impl Plug
+      @doc "If marked as skipped, returns `conn`, and calls `perform/2` otherwise."
+      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)
+        end
+      end
+    end
+  end
+
   @doc """
   When used, dispatch to the appropriate controller/view/etc.
   """