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