bf48ce26c8380202b9208095aed92f9d072abf7a
[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 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 def controller do
24 quote do
25 use Phoenix.Controller, namespace: Pleroma.Web
26
27 import Plug.Conn
28 import Pleroma.Web.Gettext
29 import Pleroma.Web.Router.Helpers
30 import Pleroma.Web.TranslationHelpers
31
32 alias Pleroma.Plugs.PlugHelper
33
34 plug(:set_put_layout)
35
36 defp set_put_layout(conn, _) do
37 put_layout(conn, Pleroma.Config.get(:app_layout, "app.html"))
38 end
39
40 # Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain
41 defp skip_plug(conn, plug_module) do
42 try do
43 plug_module.skip_plug(conn)
44 rescue
45 UndefinedFunctionError ->
46 raise "#{plug_module} is not skippable. Append `use Pleroma.Web, :plug` to its code."
47 end
48 end
49
50 # Executed just before actual controller action, invokes before-action hooks (callbacks)
51 defp action(conn, params) do
52 with %Plug.Conn{halted: false} <- maybe_halt_on_missing_oauth_scopes_check(conn) do
53 super(conn, params)
54 end
55 end
56
57 # Halts if authenticated API action neither performs nor explicitly skips OAuth scopes check
58 defp maybe_halt_on_missing_oauth_scopes_check(conn) do
59 if Pleroma.Plugs.AuthExpectedPlug.auth_expected?(conn) &&
60 not PlugHelper.plug_called_or_skipped?(conn, Pleroma.Plugs.OAuthScopesPlug) do
61 conn
62 |> render_error(
63 :forbidden,
64 "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
65 )
66 |> halt()
67 else
68 conn
69 end
70 end
71 end
72 end
73
74 def view do
75 quote do
76 use Phoenix.View,
77 root: "lib/pleroma/web/templates",
78 namespace: Pleroma.Web
79
80 # Import convenience functions from controllers
81 import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
82
83 import Pleroma.Web.ErrorHelpers
84 import Pleroma.Web.Gettext
85 import Pleroma.Web.Router.Helpers
86
87 require Logger
88
89 @doc "Same as `render/3` but wrapped in a rescue block"
90 def safe_render(view, template, assigns \\ %{}) do
91 Phoenix.View.render(view, template, assigns)
92 rescue
93 error ->
94 Logger.error(
95 "#{__MODULE__} failed to render #{inspect({view, template})}\n" <>
96 Exception.format(:error, error, __STACKTRACE__)
97 )
98
99 nil
100 end
101
102 @doc """
103 Same as `render_many/4` but wrapped in rescue block.
104 """
105 def safe_render_many(collection, view, template, assigns \\ %{}) do
106 Enum.map(collection, fn resource ->
107 as = Map.get(assigns, :as) || view.__resource__
108 assigns = Map.put(assigns, as, resource)
109 safe_render(view, template, assigns)
110 end)
111 |> Enum.filter(& &1)
112 end
113 end
114 end
115
116 def router do
117 quote do
118 use Phoenix.Router
119 # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
120 import Plug.Conn
121 import Phoenix.Controller
122 end
123 end
124
125 def channel do
126 quote do
127 # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
128 use Phoenix.Channel
129 import Pleroma.Web.Gettext
130 end
131 end
132
133 def plug do
134 quote do
135 alias Pleroma.Plugs.PlugHelper
136
137 @doc """
138 Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain.
139 """
140 def skip_plug(conn) do
141 PlugHelper.append_to_private_list(
142 conn,
143 PlugHelper.skipped_plugs_list_id(),
144 __MODULE__
145 )
146 end
147
148 @impl Plug
149 @doc "If marked as skipped, returns `conn`, and calls `perform/2` otherwise."
150 def call(%Plug.Conn{} = conn, options) do
151 if PlugHelper.plug_skipped?(conn, __MODULE__) do
152 conn
153 else
154 conn
155 |> PlugHelper.append_to_private_list(PlugHelper.called_plugs_list_id(), __MODULE__)
156 |> perform(options)
157 end
158 end
159 end
160 end
161
162 @doc """
163 When used, dispatch to the appropriate controller/view/etc.
164 """
165 defmacro __using__(which) when is_atom(which) do
166 apply(__MODULE__, which, [])
167 end
168
169 def base_url do
170 Pleroma.Web.Endpoint.url()
171 end
172 end