revert ae54c06bb4bd2389d6eb1502b842c6b632e12e40
[akkoma] / lib / pleroma / web.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web do
6 @moduledoc """
7 A module that keeps using definitions for controllers,
8 views and so on.
9
10 This can be used in your application as:
11
12 use Pleroma.Web, :controller
13 use Pleroma.Web, :view
14
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
19 Do NOT define functions inside the quoted expressions
20 below.
21 """
22
23 alias Pleroma.Helpers.AuthHelper
24 alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug
25 alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
26 alias Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug
27 alias Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug
28 alias Pleroma.Web.Plugs.OAuthScopesPlug
29 alias Pleroma.Web.Plugs.PlugHelper
30
31 def controller do
32 quote do
33 use Phoenix.Controller, namespace: Pleroma.Web
34
35 import Plug.Conn
36
37 import Pleroma.Web.Gettext
38 import Pleroma.Web.TranslationHelpers
39
40 alias Pleroma.Web.Router.Helpers, as: Routes
41
42 plug(:set_put_layout)
43
44 defp set_put_layout(conn, _) do
45 put_layout(conn, Pleroma.Config.get(:app_layout, "app.html"))
46 end
47
48 # Marks plugs intentionally skipped and blocks their execution if present in plugs chain
49 defp skip_plug(conn, plug_modules) do
50 plug_modules
51 |> List.wrap()
52 |> Enum.reduce(
53 conn,
54 fn plug_module, conn ->
55 try do
56 plug_module.skip_plug(conn)
57 rescue
58 UndefinedFunctionError ->
59 reraise(
60 "`#{plug_module}` is not skippable. Append `use Pleroma.Web, :plug` to its code.",
61 __STACKTRACE__
62 )
63 end
64 end
65 )
66 end
67
68 defp skip_auth(conn, _) do
69 skip_plug(conn, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug])
70 end
71
72 defp skip_public_check(conn, _) do
73 skip_plug(conn, EnsurePublicOrAuthenticatedPlug)
74 end
75
76 # Executed just before actual controller action, invokes before-action hooks (callbacks)
77 defp action(conn, params) do
78 with %{halted: false} = conn <-
79 maybe_drop_authentication_if_oauth_check_ignored(conn),
80 %{halted: false} = conn <- maybe_perform_public_or_authenticated_check(conn),
81 %{halted: false} = conn <- maybe_perform_authenticated_check(conn),
82 %{halted: false} = conn <- maybe_halt_on_missing_oauth_scopes_check(conn) do
83 super(conn, params)
84 end
85 end
86
87 # For non-authenticated API actions, drops auth info if OAuth scopes check was ignored
88 # (neither performed nor explicitly skipped)
89 defp maybe_drop_authentication_if_oauth_check_ignored(conn) do
90 if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) and
91 not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do
92 AuthHelper.drop_auth_info(conn)
93 else
94 conn
95 end
96 end
97
98 # Ensures instance is public -or- user is authenticated if such check was scheduled
99 defp maybe_perform_public_or_authenticated_check(conn) do
100 if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) do
101 EnsurePublicOrAuthenticatedPlug.call(conn, %{})
102 else
103 conn
104 end
105 end
106
107 # Ensures user is authenticated if such check was scheduled
108 # Note: runs prior to action even if it was already executed earlier in plug chain
109 # (since OAuthScopesPlug has option of proceeding unauthenticated)
110 defp maybe_perform_authenticated_check(conn) do
111 if PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug) do
112 EnsureAuthenticatedPlug.call(conn, %{})
113 else
114 conn
115 end
116 end
117
118 # Halts if authenticated API action neither performs nor explicitly skips OAuth scopes check
119 defp maybe_halt_on_missing_oauth_scopes_check(conn) do
120 if PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug) and
121 not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do
122 conn
123 |> render_error(
124 :forbidden,
125 "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
126 )
127 |> halt()
128 else
129 conn
130 end
131 end
132 end
133 end
134
135 def plug do
136 quote do
137 @behaviour Pleroma.Web.Plug
138 @behaviour Plug
139
140 @doc """
141 Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain.
142 """
143 def skip_plug(conn) do
144 PlugHelper.append_to_private_list(
145 conn,
146 PlugHelper.skipped_plugs_list_id(),
147 __MODULE__
148 )
149 end
150
151 @impl Plug
152 @doc """
153 Before-plug hook that
154 * ensures the plug is not skipped
155 * processes `:if_func` / `:unless_func` functional pre-run conditions
156 * adds plug to the list of called plugs and calls `perform/2` if checks are passed
157
158 Note: multiple invocations of the same plug (with different or same options) are allowed.
159 """
160 def call(%Plug.Conn{} = conn, options) do
161 if PlugHelper.plug_skipped?(conn, __MODULE__) ||
162 (options[:if_func] && !options[:if_func].(conn)) ||
163 (options[:unless_func] && options[:unless_func].(conn)) do
164 conn
165 else
166 conn =
167 PlugHelper.append_to_private_list(
168 conn,
169 PlugHelper.called_plugs_list_id(),
170 __MODULE__
171 )
172
173 apply(__MODULE__, :perform, [conn, options])
174 end
175 end
176 end
177 end
178
179 def view do
180 quote do
181 use Phoenix.View,
182 root: "lib/pleroma/web/templates",
183 namespace: Pleroma.Web
184
185 # Import convenience functions from controllers
186 import Phoenix.Controller,
187 only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]
188
189 # Include shared imports and aliases for views
190 unquote(view_helpers())
191 end
192 end
193
194 def live_view do
195 quote do
196 use Phoenix.LiveView,
197 layout: {Pleroma.Web.LayoutView, "live.html"}
198
199 unquote(view_helpers())
200 end
201 end
202
203 def live_component do
204 quote do
205 use Phoenix.LiveComponent
206
207 unquote(view_helpers())
208 end
209 end
210
211 def component do
212 quote do
213 use Phoenix.Component
214
215 unquote(view_helpers())
216 end
217 end
218
219 def router do
220 quote do
221 use Phoenix.Router
222
223 import Plug.Conn
224 import Phoenix.Controller
225 import Phoenix.LiveView.Router
226 end
227 end
228
229 def channel do
230 quote do
231 use Phoenix.Channel
232 import Pleroma.Web.Gettext
233 end
234 end
235
236 defp view_helpers do
237 quote do
238 # Use all HTML functionality (forms, tags, etc)
239 use Phoenix.HTML
240
241 # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc)
242 import Phoenix.LiveView.Helpers
243
244 # Import basic rendering functionality (render, render_layout, etc)
245 import Phoenix.View
246
247 import Pleroma.Web.ErrorHelpers
248 import Pleroma.Web.Gettext
249 alias Pleroma.Web.Router.Helpers, as: Routes
250 end
251 end
252
253 @doc """
254 When used, dispatch to the appropriate controller/view/etc.
255 """
256 defmacro __using__(which) when is_atom(which) do
257 apply(__MODULE__, which, [])
258 end
259 end