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