[#1234] Mastodon 2.4.3 hierarchical scopes initial support (WIP).
[akkoma] / lib / pleroma / web / twitter_api / controllers / util_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.TwitterAPI.UtilController do
6 use Pleroma.Web, :controller
7
8 require Logger
9
10 alias Pleroma.Activity
11 alias Pleroma.Config
12 alias Pleroma.Emoji
13 alias Pleroma.Healthcheck
14 alias Pleroma.Notification
15 alias Pleroma.Plugs.AuthenticationPlug
16 alias Pleroma.Plugs.OAuthScopesPlug
17 alias Pleroma.User
18 alias Pleroma.Web
19 alias Pleroma.Web.CommonAPI
20 alias Pleroma.Web.WebFinger
21
22 plug(
23 OAuthScopesPlug,
24 %{scopes: ["follow", "write:follows"]}
25 when action in [:do_remote_follow, :follow_import]
26 )
27
28 plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks_import)
29
30 plug(Pleroma.Plugs.SetFormatPlug when action in [:config, :version])
31
32 def help_test(conn, _params) do
33 json(conn, "ok")
34 end
35
36 def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
37 with %User{} = user <- User.get_cached_by_nickname(nick),
38 avatar = User.avatar_url(user) do
39 conn
40 |> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false})
41 else
42 _e ->
43 render(conn, "subscribe.html", %{
44 nickname: nick,
45 avatar: nil,
46 error: "Could not find user"
47 })
48 end
49 end
50
51 def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profile}}) do
52 with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile),
53 %User{ap_id: ap_id} <- User.get_cached_by_nickname(nick) do
54 conn
55 |> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id))
56 else
57 _e ->
58 render(conn, "subscribe.html", %{
59 nickname: nick,
60 avatar: nil,
61 error: "Something went wrong."
62 })
63 end
64 end
65
66 def remote_follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
67 if is_status?(acct) do
68 {:ok, object} = Pleroma.Object.Fetcher.fetch_object_from_id(acct)
69 %Activity{id: activity_id} = Activity.get_create_by_object_ap_id(object.data["id"])
70 redirect(conn, to: "/notice/#{activity_id}")
71 else
72 with {:ok, followee} <- User.get_or_fetch(acct) do
73 conn
74 |> render(follow_template(user), %{
75 error: false,
76 acct: acct,
77 avatar: User.avatar_url(followee),
78 name: followee.nickname,
79 id: followee.id
80 })
81 else
82 {:error, _reason} ->
83 render(conn, follow_template(user), %{error: :error})
84 end
85 end
86 end
87
88 defp follow_template(%User{} = _user), do: "follow.html"
89 defp follow_template(_), do: "follow_login.html"
90
91 defp is_status?(acct) do
92 case Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(acct) do
93 {:ok, %{"type" => type}} when type in ["Article", "Note", "Video", "Page", "Question"] ->
94 true
95
96 _ ->
97 false
98 end
99 end
100
101 def do_remote_follow(conn, %{
102 "authorization" => %{"name" => username, "password" => password, "id" => id}
103 }) do
104 with %User{} = followee <- User.get_cached_by_id(id),
105 {_, %User{} = user, _} <- {:auth, User.get_cached_by_nickname(username), followee},
106 {_, true, _} <- {
107 :auth,
108 AuthenticationPlug.checkpw(password, user.password_hash),
109 followee
110 },
111 {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
112 conn
113 |> render("followed.html", %{error: false})
114 else
115 # Was already following user
116 {:error, "Could not follow user:" <> _rest} ->
117 render(conn, "followed.html", %{error: "Error following account"})
118
119 {:auth, _, followee} ->
120 conn
121 |> render("follow_login.html", %{
122 error: "Wrong username or password",
123 id: id,
124 name: followee.nickname,
125 avatar: User.avatar_url(followee)
126 })
127
128 e ->
129 Logger.debug("Remote follow failed with error #{inspect(e)}")
130 render(conn, "followed.html", %{error: "Something went wrong."})
131 end
132 end
133
134 def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
135 with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)},
136 {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
137 conn
138 |> render("followed.html", %{error: false})
139 else
140 # Was already following user
141 {:error, "Could not follow user:" <> _rest} ->
142 render(conn, "followed.html", %{error: "Error following account"})
143
144 {:fetch_user, error} ->
145 Logger.debug("Remote follow failed with error #{inspect(error)}")
146 render(conn, "followed.html", %{error: "Could not find user"})
147
148 e ->
149 Logger.debug("Remote follow failed with error #{inspect(e)}")
150 render(conn, "followed.html", %{error: "Something went wrong."})
151 end
152 end
153
154 def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do
155 with {:ok, _} <- Notification.read_one(user, notification_id) do
156 json(conn, %{status: "success"})
157 else
158 {:error, message} ->
159 conn
160 |> put_resp_content_type("application/json")
161 |> send_resp(403, Jason.encode!(%{"error" => message}))
162 end
163 end
164
165 def config(%{assigns: %{format: "xml"}} = conn, _params) do
166 instance = Pleroma.Config.get(:instance)
167
168 response = """
169 <config>
170 <site>
171 <name>#{Keyword.get(instance, :name)}</name>
172 <site>#{Web.base_url()}</site>
173 <textlimit>#{Keyword.get(instance, :limit)}</textlimit>
174 <closed>#{!Keyword.get(instance, :registrations_open)}</closed>
175 </site>
176 </config>
177 """
178
179 conn
180 |> put_resp_content_type("application/xml")
181 |> send_resp(200, response)
182 end
183
184 def config(conn, _params) do
185 instance = Pleroma.Config.get(:instance)
186
187 vapid_public_key = Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
188
189 uploadlimit = %{
190 uploadlimit: to_string(Keyword.get(instance, :upload_limit)),
191 avatarlimit: to_string(Keyword.get(instance, :avatar_upload_limit)),
192 backgroundlimit: to_string(Keyword.get(instance, :background_upload_limit)),
193 bannerlimit: to_string(Keyword.get(instance, :banner_upload_limit))
194 }
195
196 data = %{
197 name: Keyword.get(instance, :name),
198 description: Keyword.get(instance, :description),
199 server: Web.base_url(),
200 textlimit: to_string(Keyword.get(instance, :limit)),
201 uploadlimit: uploadlimit,
202 closed: bool_to_val(Keyword.get(instance, :registrations_open), "0", "1"),
203 private: bool_to_val(Keyword.get(instance, :public, true), "0", "1"),
204 vapidPublicKey: vapid_public_key,
205 accountActivationRequired:
206 bool_to_val(Keyword.get(instance, :account_activation_required, false)),
207 invitesEnabled: bool_to_val(Keyword.get(instance, :invites_enabled, false)),
208 safeDMMentionsEnabled: bool_to_val(Pleroma.Config.get([:instance, :safe_dm_mentions]))
209 }
210
211 managed_config = Keyword.get(instance, :managed_config)
212
213 data =
214 if managed_config do
215 pleroma_fe = Pleroma.Config.get([:frontend_configurations, :pleroma_fe])
216 Map.put(data, "pleromafe", pleroma_fe)
217 else
218 data
219 end
220
221 json(conn, %{site: data})
222 end
223
224 defp bool_to_val(true), do: "1"
225 defp bool_to_val(_), do: "0"
226 defp bool_to_val(true, val, _), do: val
227 defp bool_to_val(_, _, val), do: val
228
229 def frontend_configurations(conn, _params) do
230 config =
231 Pleroma.Config.get(:frontend_configurations, %{})
232 |> Enum.into(%{})
233
234 json(conn, config)
235 end
236
237 def version(%{assigns: %{format: "xml"}} = conn, _params) do
238 version = Pleroma.Application.named_version()
239
240 conn
241 |> put_resp_content_type("application/xml")
242 |> send_resp(200, "<version>#{version}</version>")
243 end
244
245 def version(conn, _params) do
246 json(conn, Pleroma.Application.named_version())
247 end
248
249 def emoji(conn, _params) do
250 emoji =
251 Emoji.get_all()
252 |> Enum.map(fn {short_code, path, tags} ->
253 {short_code, %{image_url: path, tags: tags}}
254 end)
255 |> Enum.into(%{})
256
257 json(conn, emoji)
258 end
259
260 def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do
261 with {:ok, _} <- User.update_notification_settings(user, params) do
262 json(conn, %{status: "success"})
263 end
264 end
265
266 def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
267 follow_import(conn, %{"list" => File.read!(listfile.path)})
268 end
269
270 def follow_import(%{assigns: %{user: follower}} = conn, %{"list" => list}) do
271 with lines <- String.split(list, "\n"),
272 followed_identifiers <-
273 Enum.map(lines, fn line ->
274 String.split(line, ",") |> List.first()
275 end)
276 |> List.delete("Account address") do
277 PleromaJobQueue.enqueue(:background, User, [
278 :follow_import,
279 follower,
280 followed_identifiers
281 ])
282
283 json(conn, "job started")
284 end
285 end
286
287 def blocks_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
288 blocks_import(conn, %{"list" => File.read!(listfile.path)})
289 end
290
291 def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do
292 with blocked_identifiers <- String.split(list) do
293 PleromaJobQueue.enqueue(:background, User, [
294 :blocks_import,
295 blocker,
296 blocked_identifiers
297 ])
298
299 json(conn, "job started")
300 end
301 end
302
303 def change_password(%{assigns: %{user: user}} = conn, params) do
304 case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
305 {:ok, user} ->
306 with {:ok, _user} <-
307 User.reset_password(user, %{
308 password: params["new_password"],
309 password_confirmation: params["new_password_confirmation"]
310 }) do
311 json(conn, %{status: "success"})
312 else
313 {:error, changeset} ->
314 {_, {error, _}} = Enum.at(changeset.errors, 0)
315 json(conn, %{error: "New password #{error}."})
316
317 _ ->
318 json(conn, %{error: "Unable to change password."})
319 end
320
321 {:error, msg} ->
322 json(conn, %{error: msg})
323 end
324 end
325
326 def delete_account(%{assigns: %{user: user}} = conn, params) do
327 case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
328 {:ok, user} ->
329 User.delete(user)
330 json(conn, %{status: "success"})
331
332 {:error, msg} ->
333 json(conn, %{error: msg})
334 end
335 end
336
337 def disable_account(%{assigns: %{user: user}} = conn, params) do
338 case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
339 {:ok, user} ->
340 User.deactivate_async(user)
341 json(conn, %{status: "success"})
342
343 {:error, msg} ->
344 json(conn, %{error: msg})
345 end
346 end
347
348 def captcha(conn, _params) do
349 json(conn, Pleroma.Captcha.new())
350 end
351
352 def healthcheck(conn, _params) do
353 with true <- Config.get([:instance, :healthcheck]),
354 %{healthy: true} = info <- Healthcheck.system_info() do
355 json(conn, info)
356 else
357 %{healthy: false} = info ->
358 service_unavailable(conn, info)
359
360 _ ->
361 service_unavailable(conn, %{})
362 end
363 end
364
365 defp service_unavailable(conn, info) do
366 conn
367 |> put_status(:service_unavailable)
368 |> json(info)
369 end
370 end