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