util_controller.ex 12 KB
Newer Older
1
# Pleroma: A lightweight social networking server
kaniini's avatar
kaniini committed
2
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3
4
# SPDX-License-Identifier: AGPL-3.0-only

dtluna's avatar
dtluna committed
5
6
defmodule Pleroma.Web.TwitterAPI.UtilController do
  use Pleroma.Web, :controller
7

eal's avatar
eal committed
8
  require Logger
9

10
  alias Pleroma.Activity
Maksim's avatar
Maksim committed
11
  alias Pleroma.Config
Haelwenn's avatar
Haelwenn committed
12
  alias Pleroma.Emoji
Maksim's avatar
Maksim committed
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
Haelwenn's avatar
Haelwenn committed
19
20
  alias Pleroma.Web.CommonAPI
  alias Pleroma.Web.WebFinger
Roger Braun's avatar
Roger Braun committed
21

22
23
24
25
26
27
28
29
  plug(
    OAuthScopesPlug,
    %{scopes: ["follow", "write:follows"]}
    when action in [:do_remote_follow, :follow_import]
  )

  plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks_import)

30
31
32
33
  plug(
    OAuthScopesPlug,
    %{scopes: ["write:accounts"]}
    when action in [
34
           :change_email,
35
36
37
38
39
40
41
           :change_password,
           :delete_account,
           :update_notificaton_settings,
           :disable_account
         ]
  )

42
43
  plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)

Maksim's avatar
Maksim committed
44
45
  plug(Pleroma.Plugs.SetFormatPlug when action in [:config, :version])

dtluna's avatar
dtluna committed
46
47
48
49
  def help_test(conn, _params) do
    json(conn, "ok")
  end

eal's avatar
eal committed
50
  def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
Maksim's avatar
Maksim committed
51
52
    with %User{} = user <- User.get_cached_by_nickname(nick),
         avatar = User.avatar_url(user) do
eal's avatar
eal committed
53
54
55
      conn
      |> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false})
    else
lain's avatar
lain committed
56
57
58
59
60
61
      _e ->
        render(conn, "subscribe.html", %{
          nickname: nick,
          avatar: nil,
          error: "Could not find user"
        })
eal's avatar
eal committed
62
63
    end
  end
lain's avatar
lain committed
64

eal's avatar
eal committed
65
66
67
68
69
70
71
  def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profile}}) do
    with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile),
         %User{ap_id: ap_id} <- User.get_cached_by_nickname(nick) do
      conn
      |> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id))
    else
      _e ->
lain's avatar
lain committed
72
73
74
75
76
        render(conn, "subscribe.html", %{
          nickname: nick,
          avatar: nil,
          error: "Something went wrong."
        })
eal's avatar
eal committed
77
78
79
    end
  end

80
  def remote_follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
81
    if is_status?(acct) do
82
      {:ok, object} = Pleroma.Object.Fetcher.fetch_object_from_id(acct)
83
84
      %Activity{id: activity_id} = Activity.get_create_by_object_ap_id(object.data["id"])
      redirect(conn, to: "/notice/#{activity_id}")
85
    else
Maksim's avatar
Maksim committed
86
      with {:ok, followee} <- User.get_or_fetch(acct) do
87
        conn
Maksim's avatar
Maksim committed
88
        |> render(follow_template(user), %{
89
90
          error: false,
          acct: acct,
Maksim's avatar
Maksim committed
91
92
93
          avatar: User.avatar_url(followee),
          name: followee.nickname,
          id: followee.id
94
        })
Maksim's avatar
Maksim committed
95
96
97
      else
        {:error, _reason} ->
          render(conn, follow_template(user), %{error: :error})
98
      end
99
100
101
    end
  end

Maksim's avatar
Maksim committed
102
103
104
  defp follow_template(%User{} = _user), do: "follow.html"
  defp follow_template(_), do: "follow_login.html"

105
  defp is_status?(acct) do
106
    case Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(acct) do
107
108
      {:ok, %{"type" => type}}
      when type in ["Article", "Event", "Note", "Video", "Page", "Question"] ->
109
110
111
112
        true

      _ ->
        false
113
114
115
    end
  end

lain's avatar
lain committed
116
117
118
  def do_remote_follow(conn, %{
        "authorization" => %{"name" => username, "password" => password, "id" => id}
      }) do
Maksim's avatar
Maksim committed
119
120
121
122
123
124
125
    with %User{} = followee <- User.get_cached_by_id(id),
         {_, %User{} = user, _} <- {:auth, User.get_cached_by_nickname(username), followee},
         {_, true, _} <- {
           :auth,
           AuthenticationPlug.checkpw(password, user.password_hash),
           followee
         },
126
         {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
127
128
129
      conn
      |> render("followed.html", %{error: false})
    else
130
131
      # Was already following user
      {:error, "Could not follow user:" <> _rest} ->
Maksim's avatar
Maksim committed
132
        render(conn, "followed.html", %{error: "Error following account"})
133

Maksim's avatar
Maksim committed
134
      {:auth, _, followee} ->
135
        conn
lain's avatar
lain committed
136
137
138
        |> render("follow_login.html", %{
          error: "Wrong username or password",
          id: id,
Maksim's avatar
Maksim committed
139
140
          name: followee.nickname,
          avatar: User.avatar_url(followee)
lain's avatar
lain committed
141
        })
Maksim's avatar
Maksim committed
142
143
144
145

      e ->
        Logger.debug("Remote follow failed with error #{inspect(e)}")
        render(conn, "followed.html", %{error: "Something went wrong."})
146
147
    end
  end
lain's avatar
lain committed
148

149
  def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
Maksim's avatar
Maksim committed
150
    with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)},
151
         {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
152
153
154
      conn
      |> render("followed.html", %{error: false})
    else
155
156
      # Was already following user
      {:error, "Could not follow user:" <> _rest} ->
Maksim's avatar
Maksim committed
157
158
159
160
161
        render(conn, "followed.html", %{error: "Error following account"})

      {:fetch_user, error} ->
        Logger.debug("Remote follow failed with error #{inspect(error)}")
        render(conn, "followed.html", %{error: "Could not find user"})
162

163
      e ->
lain's avatar
lain committed
164
        Logger.debug("Remote follow failed with error #{inspect(e)}")
Maksim's avatar
Maksim committed
165
        render(conn, "followed.html", %{error: "Something went wrong."})
166
167
168
    end
  end

169
170
171
172
173
174
175
176
177
178
179
  def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do
    with {:ok, _} <- Notification.read_one(user, notification_id) do
      json(conn, %{status: "success"})
    else
      {:error, message} ->
        conn
        |> put_resp_content_type("application/json")
        |> send_resp(403, Jason.encode!(%{"error" => message}))
    end
  end

Maksim's avatar
Maksim committed
180
  def config(%{assigns: %{format: "xml"}} = conn, _params) do
href's avatar
href committed
181
182
    instance = Pleroma.Config.get(:instance)

Maksim's avatar
Maksim committed
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
    response = """
    <config>
    <site>
    <name>#{Keyword.get(instance, :name)}</name>
    <site>#{Web.base_url()}</site>
    <textlimit>#{Keyword.get(instance, :limit)}</textlimit>
    <closed>#{!Keyword.get(instance, :registrations_open)}</closed>
    </site>
    </config>
    """

    conn
    |> put_resp_content_type("application/xml")
    |> send_resp(200, response)
  end
lain's avatar
lain committed
198

Maksim's avatar
Maksim committed
199
200
  def config(conn, _params) do
    instance = Pleroma.Config.get(:instance)
201

Maksim's avatar
Maksim committed
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
    vapid_public_key = Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)

    uploadlimit = %{
      uploadlimit: to_string(Keyword.get(instance, :upload_limit)),
      avatarlimit: to_string(Keyword.get(instance, :avatar_upload_limit)),
      backgroundlimit: to_string(Keyword.get(instance, :background_upload_limit)),
      bannerlimit: to_string(Keyword.get(instance, :banner_upload_limit))
    }

    data = %{
      name: Keyword.get(instance, :name),
      description: Keyword.get(instance, :description),
      server: Web.base_url(),
      textlimit: to_string(Keyword.get(instance, :limit)),
      uploadlimit: uploadlimit,
      closed: bool_to_val(Keyword.get(instance, :registrations_open), "0", "1"),
      private: bool_to_val(Keyword.get(instance, :public, true), "0", "1"),
      vapidPublicKey: vapid_public_key,
      accountActivationRequired:
        bool_to_val(Keyword.get(instance, :account_activation_required, false)),
      invitesEnabled: bool_to_val(Keyword.get(instance, :invites_enabled, false)),
      safeDMMentionsEnabled: bool_to_val(Pleroma.Config.get([:instance, :safe_dm_mentions]))
    }

    managed_config = Keyword.get(instance, :managed_config)

    data =
      if managed_config do
230
        pleroma_fe = Pleroma.Config.get([:frontend_configurations, :pleroma_fe])
Maksim's avatar
Maksim committed
231
232
233
234
        Map.put(data, "pleromafe", pleroma_fe)
      else
        data
      end
235

Maksim's avatar
Maksim committed
236
    json(conn, %{site: data})
lain's avatar
lain committed
237
238
  end

Maksim's avatar
Maksim committed
239
240
241
242
243
  defp bool_to_val(true), do: "1"
  defp bool_to_val(_), do: "0"
  defp bool_to_val(true, val, _), do: val
  defp bool_to_val(_, _, val), do: val

lain's avatar
lain committed
244
245
246
247
248
249
250
251
  def frontend_configurations(conn, _params) do
    config =
      Pleroma.Config.get(:frontend_configurations, %{})
      |> Enum.into(%{})

    json(conn, config)
  end

Maksim's avatar
Maksim committed
252
  def version(%{assigns: %{format: "xml"}} = conn, _params) do
href's avatar
href committed
253
    version = Pleroma.Application.named_version()
lain's avatar
lain committed
254

Maksim's avatar
Maksim committed
255
256
257
258
    conn
    |> put_resp_content_type("application/xml")
    |> send_resp(200, "<version>#{version}</version>")
  end
lain's avatar
lain committed
259

Maksim's avatar
Maksim committed
260
261
  def version(conn, _params) do
    json(conn, Pleroma.Application.named_version())
dtluna's avatar
dtluna committed
262
  end
eal's avatar
eal committed
263
264

  def emoji(conn, _params) do
265
    emoji =
Maksim's avatar
Maksim committed
266
267
      Enum.reduce(Emoji.get_all(), %{}, fn {code, %Emoji{file: file, tags: tags}}, acc ->
        Map.put(acc, code, %{image_url: file, tags: tags})
268
269
270
      end)

    json(conn, emoji)
eal's avatar
eal committed
271
  end
eal's avatar
eal committed
272

273
274
275
276
  def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do
    with {:ok, _} <- User.update_notification_settings(user, params) do
      json(conn, %{status: "success"})
    end
eal's avatar
eal committed
277
  end
eal's avatar
eal committed
278

279
280
281
  def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
    follow_import(conn, %{"list" => File.read!(listfile.path)})
  end
lain's avatar
lain committed
282

283
  def follow_import(%{assigns: %{user: follower}} = conn, %{"list" => list}) do
284
285
286
287
288
    with lines <- String.split(list, "\n"),
         followed_identifiers <-
           Enum.map(lines, fn line ->
             String.split(line, ",") |> List.first()
           end)
289
           |> List.delete("Account address") do
290
      User.follow_import(follower, followed_identifiers)
291
292
      json(conn, "job started")
    end
eal's avatar
eal committed
293
  end
294

295
296
297
298
  def blocks_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
    blocks_import(conn, %{"list" => File.read!(listfile.path)})
  end

299
  def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do
300
    with blocked_identifiers <- String.split(list) do
301
      User.blocks_import(blocker, blocked_identifiers)
302
303
      json(conn, "job started")
    end
304
305
  end

306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
  def change_password(%{assigns: %{user: user}} = conn, params) do
    case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
      {:ok, user} ->
        with {:ok, _user} <-
               User.reset_password(user, %{
                 password: params["new_password"],
                 password_confirmation: params["new_password_confirmation"]
               }) do
          json(conn, %{status: "success"})
        else
          {:error, changeset} ->
            {_, {error, _}} = Enum.at(changeset.errors, 0)
            json(conn, %{error: "New password #{error}."})

          _ ->
            json(conn, %{error: "Unable to change password."})
        end

      {:error, msg} ->
        json(conn, %{error: msg})
    end
  end

minibikini's avatar
minibikini committed
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
  def change_email(%{assigns: %{user: user}} = conn, params) do
    case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
      {:ok, user} ->
        with {:ok, _user} <- User.change_email(user, params["email"]) do
          json(conn, %{status: "success"})
        else
          {:error, changeset} ->
            {_, {error, _}} = Enum.at(changeset.errors, 0)
            json(conn, %{error: "Email #{error}."})

          _ ->
            json(conn, %{error: "Unable to change email."})
        end

      {:error, msg} ->
        json(conn, %{error: msg})
    end
  end

348
  def delete_account(%{assigns: %{user: user}} = conn, params) do
349
    case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
350
      {:ok, user} ->
351
        User.delete(user)
352
        json(conn, %{status: "success"})
353
354
355
356
357

      {:error, msg} ->
        json(conn, %{error: msg})
    end
  end
358

359
360
361
  def disable_account(%{assigns: %{user: user}} = conn, params) do
    case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
      {:ok, user} ->
362
        User.deactivate_async(user)
363
364
365
366
367
368
369
        json(conn, %{status: "success"})

      {:error, msg} ->
        json(conn, %{error: msg})
    end
  end

370
371
372
  def captcha(conn, _params) do
    json(conn, Pleroma.Captcha.new())
  end
373
374

  def healthcheck(conn, _params) do
Maksim's avatar
Maksim committed
375
376
377
378
379
380
    with true <- Config.get([:instance, :healthcheck]),
         %{healthy: true} = info <- Healthcheck.system_info() do
      json(conn, info)
    else
      %{healthy: false} = info ->
        service_unavailable(conn, info)
381

Maksim's avatar
Maksim committed
382
383
384
385
      _ ->
        service_unavailable(conn, %{})
    end
  end
386

Maksim's avatar
Maksim committed
387
388
389
390
  defp service_unavailable(conn, info) do
    conn
    |> put_status(:service_unavailable)
    |> json(info)
391
  end
dtluna's avatar
dtluna committed
392
end