[#1234] Merge remote-tracking branch 'remotes/upstream/develop' into 1234-mastodon...
[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 Enum.reduce(Emoji.get_all(), %{}, fn {code, %Emoji{file: file, tags: tags}}, acc ->
264 Map.put(acc, code, %{image_url: file, tags: tags})
265 end)
266
267 json(conn, emoji)
268 end
269
270 def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do
271 with {:ok, _} <- User.update_notification_settings(user, params) do
272 json(conn, %{status: "success"})
273 end
274 end
275
276 def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
277 follow_import(conn, %{"list" => File.read!(listfile.path)})
278 end
279
280 def follow_import(%{assigns: %{user: follower}} = conn, %{"list" => list}) do
281 with lines <- String.split(list, "\n"),
282 followed_identifiers <-
283 Enum.map(lines, fn line ->
284 String.split(line, ",") |> List.first()
285 end)
286 |> List.delete("Account address") do
287 User.follow_import(follower, followed_identifiers)
288 json(conn, "job started")
289 end
290 end
291
292 def blocks_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
293 blocks_import(conn, %{"list" => File.read!(listfile.path)})
294 end
295
296 def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do
297 with blocked_identifiers <- String.split(list) do
298 User.blocks_import(blocker, blocked_identifiers)
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 change_email(%{assigns: %{user: user}} = conn, params) do
327 case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
328 {:ok, user} ->
329 with {:ok, _user} <- User.change_email(user, params["email"]) do
330 json(conn, %{status: "success"})
331 else
332 {:error, changeset} ->
333 {_, {error, _}} = Enum.at(changeset.errors, 0)
334 json(conn, %{error: "Email #{error}."})
335
336 _ ->
337 json(conn, %{error: "Unable to change email."})
338 end
339
340 {:error, msg} ->
341 json(conn, %{error: msg})
342 end
343 end
344
345 def delete_account(%{assigns: %{user: user}} = conn, params) do
346 case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
347 {:ok, user} ->
348 User.delete(user)
349 json(conn, %{status: "success"})
350
351 {:error, msg} ->
352 json(conn, %{error: msg})
353 end
354 end
355
356 def disable_account(%{assigns: %{user: user}} = conn, params) do
357 case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
358 {:ok, user} ->
359 User.deactivate_async(user)
360 json(conn, %{status: "success"})
361
362 {:error, msg} ->
363 json(conn, %{error: msg})
364 end
365 end
366
367 def captcha(conn, _params) do
368 json(conn, Pleroma.Captcha.new())
369 end
370
371 def healthcheck(conn, _params) do
372 with true <- Config.get([:instance, :healthcheck]),
373 %{healthy: true} = info <- Healthcheck.system_info() do
374 json(conn, info)
375 else
376 %{healthy: false} = info ->
377 service_unavailable(conn, info)
378
379 _ ->
380 service_unavailable(conn, %{})
381 end
382 end
383
384 defp service_unavailable(conn, info) do
385 conn
386 |> put_status(:service_unavailable)
387 |> json(info)
388 end
389 end