logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma

web.ex (7525B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Web.Plug do
  5. # Substitute for `call/2` which is defined with `use Pleroma.Web, :plug`
  6. @callback perform(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t()
  7. end
  8. defmodule Pleroma.Web do
  9. @moduledoc """
  10. A module that keeps using definitions for controllers,
  11. views and so on.
  12. This can be used in your application as:
  13. use Pleroma.Web, :controller
  14. use Pleroma.Web, :view
  15. The definitions below will be executed for every view,
  16. controller, etc, so keep them short and clean, focused
  17. on imports, uses and aliases.
  18. Do NOT define functions inside the quoted expressions
  19. below.
  20. """
  21. alias Pleroma.Plugs.EnsureAuthenticatedPlug
  22. alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
  23. alias Pleroma.Plugs.ExpectAuthenticatedCheckPlug
  24. alias Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug
  25. alias Pleroma.Plugs.OAuthScopesPlug
  26. alias Pleroma.Plugs.PlugHelper
  27. def controller do
  28. quote do
  29. use Phoenix.Controller, namespace: Pleroma.Web
  30. import Plug.Conn
  31. import Pleroma.Web.Gettext
  32. import Pleroma.Web.Router.Helpers
  33. import Pleroma.Web.TranslationHelpers
  34. plug(:set_put_layout)
  35. defp set_put_layout(conn, _) do
  36. put_layout(conn, Pleroma.Config.get(:app_layout, "app.html"))
  37. end
  38. # Marks plugs intentionally skipped and blocks their execution if present in plugs chain
  39. defp skip_plug(conn, plug_modules) do
  40. plug_modules
  41. |> List.wrap()
  42. |> Enum.reduce(
  43. conn,
  44. fn plug_module, conn ->
  45. try do
  46. plug_module.skip_plug(conn)
  47. rescue
  48. UndefinedFunctionError ->
  49. raise "`#{plug_module}` is not skippable. Append `use Pleroma.Web, :plug` to its code."
  50. end
  51. end
  52. )
  53. end
  54. # Executed just before actual controller action, invokes before-action hooks (callbacks)
  55. defp action(conn, params) do
  56. with %{halted: false} = conn <- maybe_drop_authentication_if_oauth_check_ignored(conn),
  57. %{halted: false} = conn <- maybe_perform_public_or_authenticated_check(conn),
  58. %{halted: false} = conn <- maybe_perform_authenticated_check(conn),
  59. %{halted: false} = conn <- maybe_halt_on_missing_oauth_scopes_check(conn) do
  60. super(conn, params)
  61. end
  62. end
  63. # For non-authenticated API actions, drops auth info if OAuth scopes check was ignored
  64. # (neither performed nor explicitly skipped)
  65. defp maybe_drop_authentication_if_oauth_check_ignored(conn) do
  66. if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) and
  67. not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do
  68. OAuthScopesPlug.drop_auth_info(conn)
  69. else
  70. conn
  71. end
  72. end
  73. # Ensures instance is public -or- user is authenticated if such check was scheduled
  74. defp maybe_perform_public_or_authenticated_check(conn) do
  75. if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) do
  76. EnsurePublicOrAuthenticatedPlug.call(conn, %{})
  77. else
  78. conn
  79. end
  80. end
  81. # Ensures user is authenticated if such check was scheduled
  82. # Note: runs prior to action even if it was already executed earlier in plug chain
  83. # (since OAuthScopesPlug has option of proceeding unauthenticated)
  84. defp maybe_perform_authenticated_check(conn) do
  85. if PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug) do
  86. EnsureAuthenticatedPlug.call(conn, %{})
  87. else
  88. conn
  89. end
  90. end
  91. # Halts if authenticated API action neither performs nor explicitly skips OAuth scopes check
  92. defp maybe_halt_on_missing_oauth_scopes_check(conn) do
  93. if PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug) and
  94. not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do
  95. conn
  96. |> render_error(
  97. :forbidden,
  98. "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
  99. )
  100. |> halt()
  101. else
  102. conn
  103. end
  104. end
  105. end
  106. end
  107. def view do
  108. quote do
  109. use Phoenix.View,
  110. root: "lib/pleroma/web/templates",
  111. namespace: Pleroma.Web
  112. # Import convenience functions from controllers
  113. import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
  114. import Pleroma.Web.ErrorHelpers
  115. import Pleroma.Web.Gettext
  116. import Pleroma.Web.Router.Helpers
  117. require Logger
  118. @doc "Same as `render/3` but wrapped in a rescue block"
  119. def safe_render(view, template, assigns \\ %{}) do
  120. Phoenix.View.render(view, template, assigns)
  121. rescue
  122. error ->
  123. Logger.error(
  124. "#{__MODULE__} failed to render #{inspect({view, template})}\n" <>
  125. Exception.format(:error, error, __STACKTRACE__)
  126. )
  127. nil
  128. end
  129. @doc """
  130. Same as `render_many/4` but wrapped in rescue block.
  131. """
  132. def safe_render_many(collection, view, template, assigns \\ %{}) do
  133. Enum.map(collection, fn resource ->
  134. as = Map.get(assigns, :as) || view.__resource__
  135. assigns = Map.put(assigns, as, resource)
  136. safe_render(view, template, assigns)
  137. end)
  138. |> Enum.filter(& &1)
  139. end
  140. end
  141. end
  142. def router do
  143. quote do
  144. use Phoenix.Router
  145. # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
  146. import Plug.Conn
  147. import Phoenix.Controller
  148. end
  149. end
  150. def channel do
  151. quote do
  152. # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
  153. use Phoenix.Channel
  154. import Pleroma.Web.Gettext
  155. end
  156. end
  157. def plug do
  158. quote do
  159. @behaviour Pleroma.Web.Plug
  160. @behaviour Plug
  161. @doc """
  162. Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain.
  163. """
  164. def skip_plug(conn) do
  165. PlugHelper.append_to_private_list(
  166. conn,
  167. PlugHelper.skipped_plugs_list_id(),
  168. __MODULE__
  169. )
  170. end
  171. @impl Plug
  172. @doc """
  173. Before-plug hook that
  174. * ensures the plug is not skipped
  175. * processes `:if_func` / `:unless_func` functional pre-run conditions
  176. * adds plug to the list of called plugs and calls `perform/2` if checks are passed
  177. Note: multiple invocations of the same plug (with different or same options) are allowed.
  178. """
  179. def call(%Plug.Conn{} = conn, options) do
  180. if PlugHelper.plug_skipped?(conn, __MODULE__) ||
  181. (options[:if_func] && !options[:if_func].(conn)) ||
  182. (options[:unless_func] && options[:unless_func].(conn)) do
  183. conn
  184. else
  185. conn =
  186. PlugHelper.append_to_private_list(
  187. conn,
  188. PlugHelper.called_plugs_list_id(),
  189. __MODULE__
  190. )
  191. apply(__MODULE__, :perform, [conn, options])
  192. end
  193. end
  194. end
  195. end
  196. @doc """
  197. When used, dispatch to the appropriate controller/view/etc.
  198. """
  199. defmacro __using__(which) when is_atom(which) do
  200. apply(__MODULE__, which, [])
  201. end
  202. def base_url do
  203. Pleroma.Web.Endpoint.url()
  204. end
  205. end