user.ex 71.2 KB
Newer Older
1
# Pleroma: A lightweight social networking server
2
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3
4
# SPDX-License-Identifier: AGPL-3.0-only

lain's avatar
lain committed
5
6
defmodule Pleroma.User do
  use Ecto.Schema
7

Haelwenn's avatar
Haelwenn committed
8
9
  import Ecto.Changeset
  import Ecto.Query
10
  import Ecto, only: [assoc: 2]
Haelwenn's avatar
Haelwenn committed
11

12
  alias Ecto.Multi
13
  alias Pleroma.Activity
14
  alias Pleroma.Config
15
  alias Pleroma.Conversation.Participation
16
  alias Pleroma.Delivery
17
  alias Pleroma.EctoType.ActivityPub.ObjectValidators
18
  alias Pleroma.Emoji
19
  alias Pleroma.FollowingRelationship
20
  alias Pleroma.Formatter
Haelwenn's avatar
Haelwenn committed
21
  alias Pleroma.HTML
22
  alias Pleroma.Keys
23
  alias Pleroma.MFA
24
25
  alias Pleroma.Notification
  alias Pleroma.Object
26
  alias Pleroma.Registration
Haelwenn's avatar
Haelwenn committed
27
  alias Pleroma.Repo
Sergey Suprunenko's avatar
Sergey Suprunenko committed
28
  alias Pleroma.RepoStreamer
Haelwenn's avatar
Haelwenn committed
29
  alias Pleroma.User
30
  alias Pleroma.UserRelationship
Haelwenn's avatar
Haelwenn committed
31
  alias Pleroma.Web
32
  alias Pleroma.Web.ActivityPub.ActivityPub
33
34
  alias Pleroma.Web.ActivityPub.Builder
  alias Pleroma.Web.ActivityPub.Pipeline
35
  alias Pleroma.Web.ActivityPub.Utils
36
  alias Pleroma.Web.CommonAPI
Maxim Filippov's avatar
Maxim Filippov committed
37
  alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
Haelwenn's avatar
Haelwenn committed
38
  alias Pleroma.Web.OAuth
39
  alias Pleroma.Web.RelMe
40
  alias Pleroma.Workers.BackgroundWorker
lain's avatar
lain committed
41

42
43
  require Logger

Maksim's avatar
Maksim committed
44
  @type t :: %__MODULE__{}
45
46
47
48
49
50
  @type account_status ::
          :active
          | :deactivated
          | :password_reset_pending
          | :confirmation_pending
          | :approval_pending
51
  @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
href's avatar
href committed
52

53
  # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
href's avatar
href committed
54
55
56
  @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/

  @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
href's avatar
href committed
57
  @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
href's avatar
href committed
58

59
  # AP ID user relationships (blocks, mutes etc.)
60
61
  # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
  @user_relationships_config [
62
63
64
65
66
67
68
69
    block: [
      blocker_blocks: :blocked_users,
      blockee_blocks: :blocker_users
    ],
    mute: [
      muter_mutes: :muted_users,
      mutee_mutes: :muter_users
    ],
70
71
72
73
74
75
76
    reblog_mute: [
      reblog_muter_mutes: :reblog_muted_users,
      reblog_mutee_mutes: :reblog_muter_users
    ],
    notification_mute: [
      notification_muter_mutes: :notification_muted_users,
      notification_mutee_mutes: :notification_muter_users
77
78
79
80
81
    ],
    # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
    inverse_subscription: [
      subscribee_subscriptions: :subscriber_users,
      subscriber_subscriptions: :subscribee_users
82
83
84
    ]
  ]

lain's avatar
lain committed
85
  schema "users" do
lain's avatar
lain committed
86
    field(:bio, :string)
87
    field(:raw_bio, :string)
lain's avatar
lain committed
88
89
90
91
92
93
    field(:email, :string)
    field(:name, :string)
    field(:nickname, :string)
    field(:password_hash, :string)
    field(:password, :string, virtual: true)
    field(:password_confirmation, :string, virtual: true)
rinpatch's avatar
rinpatch committed
94
    field(:keys, :string)
95
    field(:public_key, :string)
lain's avatar
lain committed
96
    field(:ap_id, :string)
97
    field(:avatar, :map, default: %{})
lain's avatar
lain committed
98
99
    field(:local, :boolean, default: true)
    field(:follower_address, :string)
100
    field(:following_address, :string)
101
    field(:search_rank, :float, virtual: true)
102
    field(:search_type, :integer, virtual: true)
103
    field(:tags, {:array, :string}, default: [])
rinpatch's avatar
rinpatch committed
104
    field(:last_refreshed_at, :naive_datetime_usec)
Roman Chvanikov's avatar
Roman Chvanikov committed
105
    field(:last_digest_emailed_at, :naive_datetime)
106
107
108
109
    field(:banner, :map, default: %{})
    field(:background, :map, default: %{})
    field(:note_count, :integer, default: 0)
    field(:follower_count, :integer, default: 0)
minibikini's avatar
minibikini committed
110
    field(:following_count, :integer, default: 0)
111
112
113
    field(:locked, :boolean, default: false)
    field(:confirmation_pending, :boolean, default: false)
    field(:password_reset_pending, :boolean, default: false)
114
    field(:approval_pending, :boolean, default: false)
115
    field(:registration_reason, :string, default: nil)
116
117
118
119
120
121
122
123
124
    field(:confirmation_token, :string, default: nil)
    field(:default_scope, :string, default: "public")
    field(:domain_blocks, {:array, :string}, default: [])
    field(:deactivated, :boolean, default: false)
    field(:no_rich_text, :boolean, default: false)
    field(:ap_enabled, :boolean, default: false)
    field(:is_moderator, :boolean, default: false)
    field(:is_admin, :boolean, default: false)
    field(:show_role, :boolean, default: true)
feld's avatar
feld committed
125
    field(:mastofe_settings, :map, default: nil)
126
    field(:uri, ObjectValidators.Uri, default: nil)
127
128
129
130
131
132
133
134
135
    field(:hide_followers_count, :boolean, default: false)
    field(:hide_follows_count, :boolean, default: false)
    field(:hide_followers, :boolean, default: false)
    field(:hide_follows, :boolean, default: false)
    field(:hide_favorites, :boolean, default: true)
    field(:unread_conversation_count, :integer, default: 0)
    field(:pinned_activities, {:array, :string}, default: [])
    field(:email_notifications, :map, default: %{"digest" => false})
    field(:mascot, :map, default: nil)
136
    field(:emoji, :map, default: %{})
137
    field(:pleroma_settings_store, :map, default: %{})
138
    field(:fields, {:array, :map}, default: [])
139
140
    field(:raw_fields, {:array, :map}, default: [])
    field(:discoverable, :boolean, default: false)
141
    field(:invisible, :boolean, default: false)
142
    field(:allow_following_move, :boolean, default: true)
143
    field(:skip_thread_containment, :boolean, default: false)
144
    field(:actor_type, :string, default: "Person")
145
    field(:also_known_as, {:array, :string}, default: [])
146
147
    field(:inbox, :string)
    field(:shared_inbox, :string)
148
    field(:accepts_chat_messages, :boolean, default: nil)
149

Maksim's avatar
Maksim committed
150
151
152
153
    embeds_one(
      :notification_settings,
      Pleroma.User.NotificationSetting,
      on_replace: :update
154
155
    )

lain's avatar
lain committed
156
    has_many(:notifications, Notification)
157
    has_many(:registrations, Registration)
158
    has_many(:deliveries, Delivery)
159

160
161
162
    has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
    has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)

163
164
165
166
167
    for {relationship_type,
         [
           {outgoing_relation, outgoing_relation_target},
           {incoming_relation, incoming_relation_source}
         ]} <- @user_relationships_config do
168
169
      # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
      #   :notification_muter_mutes, :subscribee_subscriptions
170
171
172
173
      has_many(outgoing_relation, UserRelationship,
        foreign_key: :source_id,
        where: [relationship_type: relationship_type]
      )
174

175
176
      # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
      #   :notification_mutee_mutes, :subscriber_subscriptions
177
178
179
180
      has_many(incoming_relation, UserRelationship,
        foreign_key: :target_id,
        where: [relationship_type: relationship_type]
      )
181

182
183
      # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
      #   :notification_muted_users, :subscriber_users
184
      has_many(outgoing_relation_target, through: [outgoing_relation, :target])
185

186
187
      # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
      #   :notification_muter_users, :subscribee_users
188
189
      has_many(incoming_relation_source, through: [incoming_relation, :source])
    end
190

191
192
    # `:blocks` is deprecated (replaced with `blocked_users` relation)
    field(:blocks, {:array, :string}, default: [])
193
194
    # `:mutes` is deprecated (replaced with `muted_users` relation)
    field(:mutes, {:array, :string}, default: [])
195
196
197
198
    # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
    field(:muted_reblogs, {:array, :string}, default: [])
    # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
    field(:muted_notifications, {:array, :string}, default: [])
199
200
    # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
    field(:subscribers, {:array, :string}, default: [])
lain's avatar
lain committed
201

202
203
204
205
206
207
    embeds_one(
      :multi_factor_authentication_settings,
      MFA.Settings,
      on_replace: :delete
    )

lain's avatar
lain committed
208
209
    timestamps()
  end
lain's avatar
lain committed
210

211
212
  for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
        @user_relationships_config do
213
214
215
    # `def blocked_users_relation/2`, `def muted_users_relation/2`,
    #   `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
    #   `def subscriber_users/2`
216
217
218
219
220
221
222
223
224
225
    def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
      target_users_query = assoc(user, unquote(outgoing_relation_target))

      if restrict_deactivated? do
        restrict_deactivated(target_users_query)
      else
        target_users_query
      end
    end

226
227
    # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
    #   `def notification_muted_users/2`, `def subscriber_users/2`
228
229
230
231
232
233
234
235
236
    def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
      __MODULE__
      |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
        user,
        restrict_deactivated?
      ])
      |> Repo.all()
    end

237
238
    # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
    #   `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
239
240
241
242
243
244
245
246
247
248
249
    def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
      __MODULE__
      |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
        user,
        restrict_deactivated?
      ])
      |> select([u], u.ap_id)
      |> Repo.all()
    end
  end

250
251
252
253
  @doc """
  Dumps Flake Id to SQL-compatible format (16-byte UUID).
  E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
  """
254
255
256
257
258
259
260
261
262
263
264
265
266
267
  def binary_id(source_id) when is_binary(source_id) do
    with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
      dumped_id
    else
      _ -> source_id
    end
  end

  def binary_id(source_ids) when is_list(source_ids) do
    Enum.map(source_ids, &binary_id/1)
  end

  def binary_id(%User{} = user), do: binary_id(user.id)

268
269
270
271
  @doc "Returns status account"
  @spec account_status(User.t()) :: account_status()
  def account_status(%User{deactivated: true}), do: :deactivated
  def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
272
  def account_status(%User{approval_pending: true}), do: :approval_pending
273

274
  def account_status(%User{confirmation_pending: true}) do
Alexander Strizhakov's avatar
Alexander Strizhakov committed
275
276
277
278
    if Config.get([:instance, :account_activation_required]) do
      :confirmation_pending
    else
      :active
279
280
    end
  end
281

282
  def account_status(%User{}), do: :active
283

Alexander Strizhakov's avatar
Alexander Strizhakov committed
284
  @spec visible_for(User.t(), User.t() | nil) ::
285
          :visible
Alexander Strizhakov's avatar
Alexander Strizhakov committed
286
287
288
289
290
          | :invisible
          | :restricted_unauthenticated
          | :deactivated
          | :confirmation_pending
  def visible_for(user, for_user \\ nil)
291

Alexander Strizhakov's avatar
Alexander Strizhakov committed
292
  def visible_for(%User{invisible: true}, _), do: :invisible
293

294
  def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
295

Alexander Strizhakov's avatar
Alexander Strizhakov committed
296
297
298
299
300
301
302
  def visible_for(%User{} = user, nil) do
    if restrict_unauthenticated?(user) do
      :restrict_unauthenticated
    else
      visible_account_status(user)
    end
  end
303

Alexander Strizhakov's avatar
Alexander Strizhakov committed
304
  def visible_for(%User{} = user, for_user) do
305
306
307
308
309
    if superuser?(for_user) do
      :visible
    else
      visible_account_status(user)
    end
310
  end
311

312
  def visible_for(_, _), do: :invisible
Alexander Strizhakov's avatar
Alexander Strizhakov committed
313

314
315
316
  defp restrict_unauthenticated?(%User{local: true}) do
    Config.restrict_unauthenticated_access?(:profiles, :local)
  end
Alexander Strizhakov's avatar
Alexander Strizhakov committed
317

318
319
  defp restrict_unauthenticated?(%User{local: _}) do
    Config.restrict_unauthenticated_access?(:profiles, :remote)
320
321
  end

Alexander Strizhakov's avatar
Alexander Strizhakov committed
322
323
  defp visible_account_status(user) do
    status = account_status(user)
324
325
326
327
328
329

    if status in [:active, :password_reset_pending] do
      :visible
    else
      status
    end
Alexander Strizhakov's avatar
Alexander Strizhakov committed
330
  end
331

332
  @spec superuser?(User.t()) :: boolean()
333
334
  def superuser?(%User{local: true, is_admin: true}), do: true
  def superuser?(%User{local: true, is_moderator: true}), do: true
335
  def superuser?(_), do: false
336

337
  @spec invisible?(User.t()) :: boolean()
338
  def invisible?(%User{invisible: true}), do: true
kaniini's avatar
kaniini committed
339
340
  def invisible?(_), do: false

341
  def avatar_url(user, options \\ []) do
lain's avatar
lain committed
342
    case user.avatar do
343
344
345
346
347
348
349
      %{"url" => [%{"href" => href} | _]} ->
        href

      _ ->
        unless options[:no_default] do
          Config.get([:assets, :default_user_avatar], "#{Web.base_url()}/images/avi.png")
        end
lain's avatar
lain committed
350
351
352
    end
  end

353
  def banner_url(user, options \\ []) do
354
    case user.banner do
lain's avatar
lain committed
355
      %{"url" => [%{"href" => href} | _]} -> href
356
      _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
lain's avatar
lain committed
357
358
359
    end
  end

360
  # Should probably be renamed or removed
minibikini's avatar
minibikini committed
361
  def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
lain's avatar
lain committed
362

363
364
  def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
  def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
lain's avatar
lain committed
365

366
  @spec ap_following(User.t()) :: String.t()
367
368
369
370
  def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
  def ap_following(%User{} = user), do: "#{ap_id(user)}/following"

  @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
371
  def restrict_deactivated(query) do
372
    from(u in query, where: u.deactivated != ^true)
373
374
  end

375
  defdelegate following_count(user), to: FollowingRelationship
376

377
378
379
380
381
382
383
384
  defp truncate_fields_param(params) do
    if Map.has_key?(params, :fields) do
      Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
    else
      params
    end
  end

385
  defp truncate_if_exists(params, key, max_length) do
Sadposter's avatar
Sadposter committed
386
    if Map.has_key?(params, key) and is_binary(params[key]) do
387
388
389
390
391
392
393
      {value, _chopped} = String.split_at(params[key], max_length)
      Map.put(params, key, value)
    else
      params
    end
  end

394
395
396
397
398
399
400
  defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params

  defp fix_follower_address(%{nickname: nickname} = params),
    do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))

  defp fix_follower_address(params), do: params

401
  def remote_user_changeset(struct \\ %User{local: false}, params) do
feld's avatar
feld committed
402
403
    bio_limit = Config.get([:instance, :user_bio_length], 5000)
    name_limit = Config.get([:instance, :user_name_length], 100)
lain's avatar
lain committed
404

rinpatch's avatar
rinpatch committed
405
406
407
408
409
410
    name =
      case params[:name] do
        name when is_binary(name) and byte_size(name) > 0 -> name
        _ -> params[:nickname]
      end

411
412
    params =
      params
rinpatch's avatar
rinpatch committed
413
      |> Map.put(:name, name)
414
      |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
415
416
      |> truncate_if_exists(:name, name_limit)
      |> truncate_if_exists(:bio, bio_limit)
417
      |> truncate_fields_param()
418
      |> fix_follower_address()
419

420
    struct
421
422
423
424
425
    |> cast(
      params,
      [
        :bio,
        :name,
426
        :emoji,
427
        :ap_id,
428
429
        :inbox,
        :shared_inbox,
430
        :nickname,
431
        :public_key,
432
433
434
435
        :avatar,
        :ap_enabled,
        :banner,
        :locked,
436
        :last_refreshed_at,
437
438
439
440
441
442
443
444
445
446
447
448
449
        :uri,
        :follower_address,
        :following_address,
        :hide_followers,
        :hide_follows,
        :hide_followers_count,
        :hide_follows_count,
        :follower_count,
        :fields,
        :following_count,
        :discoverable,
        :invisible,
        :actor_type,
450
451
        :also_known_as,
        :accepts_chat_messages
452
453
454
455
456
457
458
459
      ]
    )
    |> validate_required([:name, :ap_id])
    |> unique_constraint(:nickname)
    |> validate_format(:nickname, @email_regex)
    |> validate_length(:bio, max: bio_limit)
    |> validate_length(:name, max: name_limit)
    |> validate_fields(true)
lain's avatar
lain committed
460
461
  end

lain's avatar
lain committed
462
  def update_changeset(struct, params \\ %{}) do
feld's avatar
feld committed
463
464
    bio_limit = Config.get([:instance, :user_bio_length], 5000)
    name_limit = Config.get([:instance, :user_name_length], 100)
465

Thog's avatar
Thog committed
466
    struct
467
468
469
470
    |> cast(
      params,
      [
        :bio,
471
        :raw_bio,
472
        :name,
473
        :emoji,
474
        :avatar,
475
        :public_key,
476
477
        :inbox,
        :shared_inbox,
478
479
480
481
482
483
484
485
486
        :locked,
        :no_rich_text,
        :default_scope,
        :banner,
        :hide_follows,
        :hide_followers,
        :hide_followers_count,
        :hide_follows_count,
        :hide_favorites,
487
        :allow_following_move,
488
489
490
491
492
493
        :background,
        :show_role,
        :skip_thread_containment,
        :fields,
        :raw_fields,
        :pleroma_settings_store,
494
        :discoverable,
495
        :actor_type,
496
497
        :also_known_as,
        :accepts_chat_messages
498
499
      ]
    )
lain's avatar
lain committed
500
    |> unique_constraint(:nickname)
href's avatar
href committed
501
    |> validate_format(:nickname, local_nickname_regex())
502
503
    |> validate_length(:bio, max: bio_limit)
    |> validate_length(:name, min: 1, max: name_limit)
504
    |> validate_inclusion(:actor_type, ["Person", "Service"])
505
    |> put_fields()
506
    |> put_emoji()
507
508
509
510
511
512
513
514
    |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
    |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
    |> put_change_if_present(:banner, &put_upload(&1, :banner))
    |> put_change_if_present(:background, &put_upload(&1, :background))
    |> put_change_if_present(
      :pleroma_settings_store,
      &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
    )
515
    |> validate_fields(false)
lain's avatar
lain committed
516
517
  end

518
519
520
521
522
523
524
525
  defp put_fields(changeset) do
    if raw_fields = get_change(changeset, :raw_fields) do
      raw_fields =
        raw_fields
        |> Enum.filter(fn %{"name" => n} -> n != "" end)

      fields =
        raw_fields
526
        |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
527
528
529
530
531
532
533
534
535

      changeset
      |> put_change(:raw_fields, raw_fields)
      |> put_change(:fields, fields)
    else
      changeset
    end
  end

536
537
538
539
540
541
  defp parse_fields(value) do
    value
    |> Formatter.linkify(mentions_format: :full)
    |> elem(0)
  end

542
  defp put_emoji(changeset) do
543
544
545
546
547
548
549
550
551
552
553
554
555
556
    emojified_fields = [:bio, :name, :raw_fields]

    if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do
      bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio))
      name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name))

      emoji = Map.merge(bio, name)

      emoji =
        changeset
        |> get_field(:raw_fields)
        |> Enum.reduce(emoji, fn x, acc ->
          Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"]))
        end)
557
558
559
560
561
562
563

      put_change(changeset, :emoji, emoji)
    else
      changeset
    end
  end

564
  defp put_change_if_present(changeset, map_field, value_function) do
565
566
567
    with {:ok, value} <- fetch_change(changeset, map_field),
         {:ok, new_value} <- value_function.(value) do
      put_change(changeset, map_field, new_value)
568
    else
569
      _ -> changeset
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
    end
  end

  defp put_upload(value, type) do
    with %Plug.Upload{} <- value,
         {:ok, object} <- ActivityPub.upload(value, type: type) do
      {:ok, object.data}
    end
  end

  def update_as_admin_changeset(struct, params) do
    struct
    |> update_changeset(params)
    |> cast(params, [:email])
    |> delete_change(:also_known_as)
    |> unique_constraint(:email)
    |> validate_format(:email, @email_regex)
Alexander Strizhakov's avatar
Alexander Strizhakov committed
587
    |> validate_inclusion(:actor_type, ["Person", "Service"])
588
589
  end

Alexander Strizhakov's avatar
Alexander Strizhakov committed
590
  @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
591
592
593
594
595
596
597
598
599
600
601
  def update_as_admin(user, params) do
    params = Map.put(params, "password_confirmation", params["password"])
    changeset = update_as_admin_changeset(user, params)

    if params["password"] do
      reset_password(user, changeset, params)
    else
      User.update_and_set_cache(changeset)
    end
  end

Roger Braun's avatar
Roger Braun committed
602
  def password_update_changeset(struct, params) do
603
604
605
606
    struct
    |> cast(params, [:password, :password_confirmation])
    |> validate_required([:password, :password_confirmation])
    |> validate_confirmation(:password)
607
608
    |> put_password_hash()
    |> put_change(:password_reset_pending, false)
609
610
  end

Alexander Strizhakov's avatar
Alexander Strizhakov committed
611
  @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
612
613
614
615
616
  def reset_password(%User{} = user, params) do
    reset_password(user, user, params)
  end

  def reset_password(%User{id: user_id} = user, struct, params) do
617
618
    multi =
      Multi.new()
619
      |> Multi.update(:user, password_update_changeset(struct, params))
620
621
622
623
624
625
      |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
      |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))

    case Repo.transaction(multi) do
      {:ok, %{user: user} = _} -> set_cache(user)
      {:error, _, changeset, _} -> {:error, changeset}
Roger Braun's avatar
Roger Braun committed
626
627
628
    end
  end

629
630
631
632
633
634
635
  def update_password_reset_pending(user, value) do
    user
    |> change()
    |> put_change(:password_reset_pending, value)
    |> update_and_set_cache()
  end

636
637
638
639
640
  def force_password_reset_async(user) do
    BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
  end

  @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
641
  def force_password_reset(user), do: update_password_reset_pending(user, true)
642

643
  # Used to auto-register LDAP accounts which won't have a password hash stored locally
644
  def register_changeset_ldap(struct, params = %{password: password})
645
646
647
      when is_nil(password) do
    params = Map.put_new(params, :accepts_chat_messages, true)

648
649
650
651
652
653
654
    params =
      if Map.has_key?(params, :email) do
        Map.put_new(params, :email, params[:email])
      else
        params
      end

655
656
657
658
    struct
    |> cast(params, [
      :name,
      :nickname,
659
      :email,
660
661
      :accepts_chat_messages
    ])
662
    |> validate_required([:name, :nickname])
663
664
665
666
667
668
669
670
    |> unique_constraint(:nickname)
    |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
    |> validate_format(:nickname, local_nickname_regex())
    |> put_ap_id()
    |> unique_constraint(:ap_id)
    |> put_following_and_follower_address()
  end

671
  def register_changeset(struct, params \\ %{}, opts \\ []) do
feld's avatar
feld committed
672
673
    bio_limit = Config.get([:instance, :user_bio_length], 5000)
    name_limit = Config.get([:instance, :user_name_length], 100)
674
    reason_limit = Config.get([:instance, :registration_reason_length], 500)
675
    params = Map.put_new(params, :accepts_chat_messages, true)
676

677
678
    need_confirmation? =
      if is_nil(opts[:need_confirmation]) do
feld's avatar
feld committed
679
        Config.get([:instance, :account_activation_required])
680
      else
681
        opts[:need_confirmation]
682
683
      end

684
685
686
687
688
689
690
    need_approval? =
      if is_nil(opts[:need_approval]) do
        Config.get([:instance, :account_approval_required])
      else
        opts[:need_approval]
      end

minibikini's avatar
minibikini committed
691
    struct
692
    |> confirmation_changeset(need_confirmation: need_confirmation?)
693
    |> approval_changeset(need_approval: need_approval?)
694
695
696
697
698
699
700
701
    |> cast(params, [
      :bio,
      :raw_bio,
      :email,
      :name,
      :nickname,
      :password,
      :password_confirmation,
702
      :emoji,
703
704
      :accepts_chat_messages,
      :registration_reason
705
    ])
minibikini's avatar
minibikini committed
706
707
708
    |> validate_required([:name, :nickname, :password, :password_confirmation])
    |> validate_confirmation(:password)
    |> unique_constraint(:email)
709
710
711
712
713
714
715
716
    |> validate_format(:email, @email_regex)
    |> validate_change(:email, fn :email, email ->
      valid? =
        Config.get([User, :email_blacklist])
        |> Enum.all?(fn blacklisted_domain ->
          !String.ends_with?(email, ["@" <> blacklisted_domain, "." <> blacklisted_domain])
        end)

717
      if valid?, do: [], else: [email: "Invalid email"]
718
    end)
minibikini's avatar
minibikini committed
719
    |> unique_constraint(:nickname)
feld's avatar
feld committed
720
    |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
minibikini's avatar
minibikini committed
721
722
723
    |> validate_format(:nickname, local_nickname_regex())
    |> validate_length(:bio, max: bio_limit)
    |> validate_length(:name, min: 1, max: name_limit)
724
    |> validate_length(:registration_reason, max: reason_limit)
minibikini's avatar
minibikini committed
725
726
727
728
729
730
    |> maybe_validate_required_email(opts[:external])
    |> put_password_hash
    |> put_ap_id()
    |> unique_constraint(:ap_id)
    |> put_following_and_follower_address()
  end
Ivan Tashkinov's avatar
Ivan Tashkinov committed
731

minibikini's avatar
minibikini committed
732
  def maybe_validate_required_email(changeset, true), do: changeset
733
734

  def maybe_validate_required_email(changeset, _) do
feld's avatar
feld committed
735
    if Config.get([:instance, :account_activation_required]) do
736
737
738
739
740
      validate_required(changeset, [:email])
    else
      changeset
    end
  end
lain's avatar
lain committed
741

minibikini's avatar
minibikini committed
742
743
744
745
  defp put_ap_id(changeset) do
    ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
    put_change(changeset, :ap_id, ap_id)
  end
746

minibikini's avatar
minibikini committed
747
748
  defp put_following_and_follower_address(changeset) do
    followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
lain's avatar
lain committed
749

minibikini's avatar
minibikini committed
750
751
    changeset
    |> put_change(:follower_address, followers)
lain's avatar
lain committed
752
753
  end

754
  defp autofollow_users(user) do
feld's avatar
feld committed
755
    candidates = Config.get([:instance, :autofollowed_nicknames])
756
757

    autofollowed_users =
758
      User.Query.build(%{nickname: candidates, local: true, deactivated: false})
759
760
      |> Repo.all()

lain's avatar
lain committed
761
    follow_all(user, autofollowed_users)
762
763
  end

764
765
  @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
  def register(%Ecto.Changeset{} = changeset) do
minibikini's avatar
minibikini committed
766
767
    with {:ok, user} <- Repo.insert(changeset) do
      post_register_action(user)
768
769
770
771
772
    end
  end

  def post_register_action(%User{} = user) do
    with {:ok, user} <- autofollow_users(user),
minibikini's avatar
minibikini committed
773
         {:ok, user} <- set_cache(user),
Maksim's avatar
Maksim committed
774
775
         {:ok, _} <- send_welcome_email(user),
         {:ok, _} <- send_welcome_message(user),
Ilja's avatar
Ilja committed
776
         {:ok, _} <- send_welcome_chat_message(user),
777
         {:ok, _} <- try_send_confirmation_email(user) do
778
779
780
781
      {:ok, user}
    end
  end

Maksim's avatar
Maksim committed
782
783
784
785
786
787
788
789
790
  def send_welcome_message(user) do
    if User.WelcomeMessage.enabled?() do
      User.WelcomeMessage.post_message(user)
      {:ok, :enqueued}
    else
      {:ok, :noop}
    end
  end

Ilja's avatar
Ilja committed
791
792
793
794
795
796
797
798
799
  def send_welcome_chat_message(user) do
    if User.WelcomeChatMessage.enabled?() do
      User.WelcomeChatMessage.post_message(user)
      {:ok, :enqueued}
    else
      {:ok, :noop}
    end
  end

800
  def send_welcome_email(%User{email: email} = user) when is_binary(email) do
Maksim's avatar
Maksim committed
801
802
803
804
805
806
807
    if User.WelcomeEmail.enabled?() do
      User.WelcomeEmail.send_email(user)
      {:ok, :enqueued}
    else
      {:ok, :noop}
    end
  end
Maksim's avatar
Maksim committed
808

809
  def send_welcome_email(_), do: {:ok, :noop}
Maksim's avatar
Maksim committed
810

811
812
813
814
  @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
  def try_send_confirmation_email(%User{confirmation_pending: true} = user) do
    if Config.get([:instance, :account_activation_required]) do
      send_confirmation_email(user)
815
      {:ok, :enqueued}
816
817
818
819
820
    else
      {:ok, :noop}
    end
  end

821
822
823
824
825
826
827
828
829
  def try_send_confirmation_email(_), do: {:ok, :noop}

  @spec send_confirmation_email(Uset.t()) :: User.t()
  def send_confirmation_email(%User{} = user) do
    user
    |> Pleroma.Emails.UserEmail.account_confirmation_email()
    |> Pleroma.Emails.Mailer.deliver_async()

    user
830
831
  end

832
833
834
835
836
  def needs_update?(%User{local: true}), do: false

  def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true

  def needs_update?(%User{local: false} = user) do
837
    NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
838
839
840
841
  end

  def needs_update?(_), do: true

Maksim's avatar
Maksim committed
842
  @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
843
844

  # "Locked" (self-locked) users demand explicit authorization of follow requests
845
  def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
846
    follow(follower, followed, :follow_pending)
847
848
849
850
851
852
853
  end

  def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
    follow(follower, followed)
  end

  def maybe_direct_follow(%User{} = follower, %User{} = followed) do
minibikini's avatar
minibikini committed
854
    if not ap_enabled?(followed) do
855
      follow(follower, followed)
856
857
858
859
860
    else
      {:ok, follower}
    end
  end

861
  @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
lain's avatar
lain committed
862
863
  @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
  def follow_all(follower, followeds) do
minibikini's avatar
minibikini committed
864
865
    followeds
    |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
866
    |> Enum.each(&follow(follower, &1, :follow_accept))
lain's avatar
lain committed
867

lain's avatar
lain committed
868
    set_cache(follower)
lain's avatar
lain committed
869
870
  end

871
872
  defdelegate following(user), to: FollowingRelationship

873
  def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
feld's avatar
feld committed
874
    deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
875

876
    cond do
877
878
      followed.deactivated ->
        {:error, "Could not follow user: #{followed.nickname} is deactivated."}
lain's avatar
lain committed
879

880
      deny_follow_blocked and blocks?(followed, follower) ->
881
        {:error, "Could not follow user: #{followed.nickname} blocked you."}
lain's avatar
lain committed
882

883
      true ->
884
        FollowingRelationship.follow(follower, followed, state)
885

886
887
        {:ok, _} = update_follower_count(followed)

minibikini's avatar
minibikini committed
888
889
        follower
        |> update_following_count()
890
    end
lain's avatar
lain committed
891
  end
lain's avatar
lain committed
892

893
894
895
896
  def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
    {:error, "Not subscribed!"}
  end

897
  @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
lain's avatar
lain committed
898
  def unfollow(%User{} = follower, %User{} = followed) do
899
900
901
902
903
904
905
906
907
908
909
    case do_unfollow(follower, followed) do
      {:ok, follower, followed} ->
        {:ok, follower, Utils.fetch_latest_follow(follower, followed)}

      error ->
        error
    end
  end

  @spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()}
  defp do_unfollow(%User{} = follower, %User{} = followed) do
minibikini's avatar
minibikini committed
910
    case get_follow_state(follower, followed) do
911
      state when state in [:follow_pending, :follow_accept] ->
912
913
        FollowingRelationship.unfollow(follower, followed)
        {:ok, followed} = update_follower_count(followed)
914

915
916
917
        {:ok, follower} =
          follower
          |> update_following_count()
918

919
        {:ok, follower, followed}
920

921
922
      nil ->
        {:error, "Not subscribed!"}
923
    end
lain's avatar
lain committed
924
  end
925

926
  defdelegate following?(follower, followed), to: FollowingRelationship
lain's avatar
lain committed
927

928
  @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
minibikini's avatar
minibikini committed
929
930
  def get_follow_state(%User{} = follower, %User{} = following) do
    following_relationship = FollowingRelationship.get(follower, following)
931
932
    get_follow_state(follower, following, following_relationship)
  end
minibikini's avatar
minibikini committed
933

934
935
936
937
938
  def get_follow_state(
        %User{} = follower,
        %User{} = following,
        following_relationship
      ) do
minibikini's avatar
minibikini committed
939
940
941
    case {following_relationship, following.local} do
      {nil, false} ->
        case Utils.fetch_latest_follow(follower, following) do
942
943
944
945
946
          %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
            FollowingRelationship.state_to_enum(state)

          _ ->
            nil
minibikini's avatar
minibikini committed
947
948
949
950
951
952
953
954
955
956
        end

      {%{state: state}, _} ->
        state

      {nil, _} ->
        nil
    end
  end

957
  def locked?(%User{} = user) do
958
    user.locked || false
959
960
  end

961
962
963
964
  def get_by_id(id) do
    Repo.get_by(User, id: id)
  end

lain's avatar
lain committed
965
966
967
968
  def get_by_ap_id(ap_id) do
    Repo.get_by(User, ap_id: ap_id)
  end

969
970
971
972
973
974
975
  def get_all_by_ap_id(ap_ids) do
    from(u in __MODULE__,
      where: u.ap_id in ^ap_ids
    )
    |> Repo.all()
  end

Maksim's avatar
Maksim committed
976
977
978
979
980
  def get_all_by_ids(ids) do
    from(u in __MODULE__, where: u.id in ^ids)
    |> Repo.all()
  end

981
982
  # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
  # of the ap_id and the domain and tries to get that user
983
984
985
986
987
  def get_by_guessed_nickname(ap_id) do
    domain = URI.parse(ap_id).host
    name = List.last(String.split(ap_id, "/"))
    nickname = "#{name}@#{domain}"

minibikini's avatar
minibikini committed
988
    get_cached_by_nickname(nickname)
989
990
  end

minibikini's avatar
minibikini committed
991
992
993
994
  def set_cache({:ok, user}), do: set_cache(user)
  def set_cache({:error, err}), do: {:error, err}

  def set_cache(%User{} = user) do
995
996
    Cachex.put(:user_cache, "ap_id:#{user