user.ex 74.2 KB
Newer Older
1
# Pleroma: A lightweight social networking server
Haelwenn's avatar
Haelwenn committed
2
# Copyright © 2017-2021 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
28
  alias Pleroma.Repo
  alias Pleroma.User
29
  alias Pleroma.UserRelationship
Haelwenn's avatar
Haelwenn committed
30
  alias Pleroma.Web
31
  alias Pleroma.Web.ActivityPub.ActivityPub
32
33
  alias Pleroma.Web.ActivityPub.Builder
  alias Pleroma.Web.ActivityPub.Pipeline
34
  alias Pleroma.Web.ActivityPub.Utils
35
  alias Pleroma.Web.CommonAPI
Maxim Filippov's avatar
Maxim Filippov committed
36
  alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
Haelwenn's avatar
Haelwenn committed
37
  alias Pleroma.Web.OAuth
38
  alias Pleroma.Web.RelMe
39
  alias Pleroma.Workers.BackgroundWorker
lain's avatar
lain committed
40

41
42
  require Logger

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

52
  # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
href's avatar
href committed
53
54
55
  @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
56
  @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
href's avatar
href committed
57

58
  # AP ID user relationships (blocks, mutes etc.)
59
60
  # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
  @user_relationships_config [
61
62
63
64
65
66
67
68
    block: [
      blocker_blocks: :blocked_users,
      blockee_blocks: :blocker_users
    ],
    mute: [
      muter_mutes: :muted_users,
      mutee_mutes: :muter_users
    ],
69
70
71
72
73
74
75
    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
76
77
78
79
80
    ],
    # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
    inverse_subscription: [
      subscribee_subscriptions: :subscriber_users,
      subscriber_subscriptions: :subscribee_users
81
82
83
    ]
  ]

84
85
  @cachex Pleroma.Config.get([:cachex, :provider], Cachex)

lain's avatar
lain committed
86
  schema "users" do
87
    field(:bio, :string, default: "")
88
    field(:raw_bio, :string)
lain's avatar
lain committed
89
90
91
92
93
94
    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
95
    field(:keys, :string)
96
    field(:public_key, :string)
lain's avatar
lain committed
97
    field(:ap_id, :string)
98
    field(:avatar, :map, default: %{})
lain's avatar
lain committed
99
100
    field(:local, :boolean, default: true)
    field(:follower_address, :string)
101
    field(:following_address, :string)
102
    field(:search_rank, :float, virtual: true)
103
    field(:search_type, :integer, virtual: true)
104
    field(:tags, {:array, :string}, default: [])
rinpatch's avatar
rinpatch committed
105
    field(:last_refreshed_at, :naive_datetime_usec)
Roman Chvanikov's avatar
Roman Chvanikov committed
106
    field(:last_digest_emailed_at, :naive_datetime)
107
108
109
110
    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
111
    field(:following_count, :integer, default: 0)
112
    field(:is_locked, :boolean, default: false)
113
    field(:is_confirmed, :boolean, default: true)
114
    field(:password_reset_pending, :boolean, default: false)
115
    field(:is_approved, :boolean, default: true)
116
    field(:registration_reason, :string, default: nil)
117
118
119
    field(:confirmation_token, :string, default: nil)
    field(:default_scope, :string, default: "public")
    field(:domain_blocks, {:array, :string}, default: [])
120
    field(:is_active, :boolean, default: true)
121
122
123
124
125
    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
126
    field(:mastofe_settings, :map, default: nil)
127
    field(:uri, ObjectValidators.Uri, default: nil)
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(: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
    field(:raw_fields, {:array, :map}, default: [])
140
    field(:is_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, ObjectValidators.ObjectID}, default: [])
146
147
    field(:inbox, :string)
    field(:shared_inbox, :string)
148
    field(:accepts_chat_messages, :boolean, default: nil)
minibikini's avatar
minibikini committed
149
    field(:last_active_at, :naive_datetime)
150
    field(:disclose_client, :boolean, default: true)
151

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

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

162
163
164
    has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
    has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)

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

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

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

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

193
194
    # `:blocks` is deprecated (replaced with `blocked_users` relation)
    field(:blocks, {:array, :string}, default: [])
195
196
    # `:mutes` is deprecated (replaced with `muted_users` relation)
    field(:mutes, {:array, :string}, default: [])
197
198
199
200
    # `: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: [])
201
202
    # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
    field(:subscribers, {:array, :string}, default: [])
lain's avatar
lain committed
203

204
205
206
207
208
209
    embeds_one(
      :multi_factor_authentication_settings,
      MFA.Settings,
      on_replace: :delete
    )

lain's avatar
lain committed
210
211
    timestamps()
  end
lain's avatar
lain committed
212

213
214
  for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
        @user_relationships_config do
215
216
217
    # `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`
218
219
220
221
    def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
      target_users_query = assoc(user, unquote(outgoing_relation_target))

      if restrict_deactivated? do
feld's avatar
feld committed
222
223
        target_users_query
        |> User.Query.build(%{deactivated: false})
224
225
226
227
228
      else
        target_users_query
      end
    end

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

240
241
    # `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`
242
243
244
245
246
247
248
249
250
251
252
    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

253
  def cached_blocked_users_ap_ids(user) do
254
    @cachex.fetch!(:user_cache, "blocked_users_ap_ids:#{user.ap_id}", fn _ ->
255
256
257
258
259
      blocked_users_ap_ids(user)
    end)
  end

  def cached_muted_users_ap_ids(user) do
260
    @cachex.fetch!(:user_cache, "muted_users_ap_ids:#{user.ap_id}", fn _ ->
261
262
263
264
      muted_users_ap_ids(user)
    end)
  end

Alexander Strizhakov's avatar
Alexander Strizhakov committed
265
266
267
268
269
270
271
  defdelegate following_count(user), to: FollowingRelationship
  defdelegate following(user), to: FollowingRelationship
  defdelegate following?(follower, followed), to: FollowingRelationship
  defdelegate following_ap_ids(user), to: FollowingRelationship
  defdelegate get_follow_requests(user), to: FollowingRelationship
  defdelegate search(query, opts \\ []), to: User.Search

272
273
274
275
  @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>>
  """
276
277
278
279
280
281
282
283
284
285
286
287
288
289
  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)

290
291
  @doc "Returns status account"
  @spec account_status(User.t()) :: account_status()
292
  def account_status(%User{is_active: false}), do: :deactivated
293
  def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
294
  def account_status(%User{local: true, is_approved: false}), do: :approval_pending
295
  def account_status(%User{local: true, is_confirmed: false}), do: :confirmation_pending
296
  def account_status(%User{}), do: :active
297

Alexander Strizhakov's avatar
Alexander Strizhakov committed
298
  @spec visible_for(User.t(), User.t() | nil) ::
299
          :visible
Alexander Strizhakov's avatar
Alexander Strizhakov committed
300
301
302
303
304
          | :invisible
          | :restricted_unauthenticated
          | :deactivated
          | :confirmation_pending
  def visible_for(user, for_user \\ nil)
305

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

308
  def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
309

Alexander Strizhakov's avatar
Alexander Strizhakov committed
310
311
312
313
314
315
316
  def visible_for(%User{} = user, nil) do
    if restrict_unauthenticated?(user) do
      :restrict_unauthenticated
    else
      visible_account_status(user)
    end
  end
317

Alexander Strizhakov's avatar
Alexander Strizhakov committed
318
  def visible_for(%User{} = user, for_user) do
319
320
321
322
323
    if superuser?(for_user) do
      :visible
    else
      visible_account_status(user)
    end
324
  end
325

326
  def visible_for(_, _), do: :invisible
Alexander Strizhakov's avatar
Alexander Strizhakov committed
327

328
329
330
  defp restrict_unauthenticated?(%User{local: true}) do
    Config.restrict_unauthenticated_access?(:profiles, :local)
  end
Alexander Strizhakov's avatar
Alexander Strizhakov committed
331

332
333
  defp restrict_unauthenticated?(%User{local: _}) do
    Config.restrict_unauthenticated_access?(:profiles, :remote)
334
335
  end

Alexander Strizhakov's avatar
Alexander Strizhakov committed
336
337
  defp visible_account_status(user) do
    status = account_status(user)
338
339
340
341
342
343

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

346
  @spec superuser?(User.t()) :: boolean()
347
348
  def superuser?(%User{local: true, is_admin: true}), do: true
  def superuser?(%User{local: true, is_moderator: true}), do: true
349
  def superuser?(_), do: false
350

351
  @spec invisible?(User.t()) :: boolean()
352
  def invisible?(%User{invisible: true}), do: true
kaniini's avatar
kaniini committed
353
354
  def invisible?(_), do: false

355
  def avatar_url(user, options \\ []) do
lain's avatar
lain committed
356
    case user.avatar do
357
358
359
360
361
362
363
      %{"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
364
365
366
    end
  end

367
  def banner_url(user, options \\ []) do
368
    case user.banner do
lain's avatar
lain committed
369
      %{"url" => [%{"href" => href} | _]} -> href
370
      _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
lain's avatar
lain committed
371
372
373
    end
  end

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

377
378
  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
379

380
  @spec ap_following(User.t()) :: String.t()
381
382
383
  def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
  def ap_following(%User{} = user), do: "#{ap_id(user)}/following"

384
385
386
387
388
389
390
391
  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

392
  defp truncate_if_exists(params, key, max_length) do
Sadposter's avatar
Sadposter committed
393
    if Map.has_key?(params, key) and is_binary(params[key]) do
394
395
396
397
398
399
400
      {value, _chopped} = String.split_at(params[key], max_length)
      Map.put(params, key, value)
    else
      params
    end
  end

401
402
403
404
405
406
407
  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

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

rinpatch's avatar
rinpatch committed
412
413
414
415
416
417
    name =
      case params[:name] do
        name when is_binary(name) and byte_size(name) > 0 -> name
        _ -> params[:nickname]
      end

418
419
    params =
      params
rinpatch's avatar
rinpatch committed
420
      |> Map.put(:name, name)
421
      |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
422
423
      |> truncate_if_exists(:name, name_limit)
      |> truncate_if_exists(:bio, bio_limit)
424
      |> truncate_fields_param()
425
      |> fix_follower_address()
426

427
    struct
428
429
430
431
    |> cast(
      params,
      [
        :bio,
432
        :emoji,
433
        :ap_id,
434
435
        :inbox,
        :shared_inbox,
436
        :nickname,
437
        :public_key,
438
439
440
        :avatar,
        :ap_enabled,
        :banner,
441
        :is_locked,
442
        :last_refreshed_at,
443
444
445
446
447
448
449
450
451
452
        :uri,
        :follower_address,
        :following_address,
        :hide_followers,
        :hide_follows,
        :hide_followers_count,
        :hide_follows_count,
        :follower_count,
        :fields,
        :following_count,
453
        :is_discoverable,
454
455
        :invisible,
        :actor_type,
456
457
        :also_known_as,
        :accepts_chat_messages
458
459
      ]
    )
460
461
462
    |> cast(params, [:name], empty_values: [])
    |> validate_required([:ap_id])
    |> validate_required([:name], trim: false)
463
464
465
466
467
    |> unique_constraint(:nickname)
    |> validate_format(:nickname, @email_regex)
    |> validate_length(:bio, max: bio_limit)
    |> validate_length(:name, max: name_limit)
    |> validate_fields(true)
468
469
470
471
472
473
474
475
476
477
478
479
    |> validate_non_local()
  end

  defp validate_non_local(cng) do
    local? = get_field(cng, :local)

    if local? do
      cng
      |> add_error(:local, "User is local, can't update with this changeset.")
    else
      cng
    end
lain's avatar
lain committed
480
481
  end

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

Thog's avatar
Thog committed
486
    struct
487
488
489
490
    |> cast(
      params,
      [
        :bio,
491
        :raw_bio,
492
        :name,
493
        :emoji,
494
        :avatar,
495
        :public_key,
496
497
        :inbox,
        :shared_inbox,
498
        :is_locked,
499
500
501
502
503
504
505
506
        :no_rich_text,
        :default_scope,
        :banner,
        :hide_follows,
        :hide_followers,
        :hide_followers_count,
        :hide_follows_count,
        :hide_favorites,
507
        :allow_following_move,
Alex Gleason's avatar
Alex Gleason committed
508
        :also_known_as,
509
510
511
512
513
514
        :background,
        :show_role,
        :skip_thread_containment,
        :fields,
        :raw_fields,
        :pleroma_settings_store,
515
        :is_discoverable,
516
        :actor_type,
feld's avatar
feld committed
517
518
        :accepts_chat_messages,
        :disclose_client
519
520
      ]
    )
lain's avatar
lain committed
521
    |> unique_constraint(:nickname)
href's avatar
href committed
522
    |> validate_format(:nickname, local_nickname_regex())
523
524
    |> validate_length(:bio, max: bio_limit)
    |> validate_length(:name, min: 1, max: name_limit)
525
    |> validate_inclusion(:actor_type, ["Person", "Service"])
526
    |> put_fields()
527
    |> put_emoji()
528
529
530
531
532
533
534
535
    |> 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)}
    )
536
    |> validate_fields(false)
lain's avatar
lain committed
537
538
  end

539
540
541
542
543
544
545
546
  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
547
        |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
548
549
550
551
552
553
554
555
556

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

557
558
559
560
561
562
  defp parse_fields(value) do
    value
    |> Formatter.linkify(mentions_format: :full)
    |> elem(0)
  end

563
  defp put_emoji(changeset) do
564
565
566
567
568
569
570
571
572
573
574
575
576
577
    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)
578
579
580
581
582
583
584

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

585
  defp put_change_if_present(changeset, map_field, value_function) do
586
587
588
    with {:ok, value} <- fetch_change(changeset, map_field),
         {:ok, new_value} <- value_function.(value) do
      put_change(changeset, map_field, new_value)
589
    else
590
      _ -> changeset
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
    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
608
    |> validate_inclusion(:actor_type, ["Person", "Service"])
609
610
  end

Alexander Strizhakov's avatar
Alexander Strizhakov committed
611
  @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
612
613
614
615
616
617
618
619
620
621
622
  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
623
  def password_update_changeset(struct, params) do
624
625
626
627
    struct
    |> cast(params, [:password, :password_confirmation])
    |> validate_required([:password, :password_confirmation])
    |> validate_confirmation(:password)
628
629
    |> put_password_hash()
    |> put_change(:password_reset_pending, false)
630
631
  end

Alexander Strizhakov's avatar
Alexander Strizhakov committed
632
  @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
633
634
635
636
637
  def reset_password(%User{} = user, params) do
    reset_password(user, user, params)
  end

  def reset_password(%User{id: user_id} = user, struct, params) do
638
639
    multi =
      Multi.new()
640
      |> Multi.update(:user, password_update_changeset(struct, params))
641
642
643
644
645
646
      |> 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
647
648
649
    end
  end

650
651
652
653
654
655
656
  def update_password_reset_pending(user, value) do
    user
    |> change()
    |> put_change(:password_reset_pending, value)
    |> update_and_set_cache()
  end

657
658
659
660
661
  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()}
662
  def force_password_reset(user), do: update_password_reset_pending(user, true)
663

664
  # Used to auto-register LDAP accounts which won't have a password hash stored locally
665
  def register_changeset_ldap(struct, params = %{password: password})
666
667
668
      when is_nil(password) do
    params = Map.put_new(params, :accepts_chat_messages, true)

669
670
671
672
673
674
675
    params =
      if Map.has_key?(params, :email) do
        Map.put_new(params, :email, params[:email])
      else
        params
      end

676
677
678
679
    struct
    |> cast(params, [
      :name,
      :nickname,
680
      :email,
681
682
      :accepts_chat_messages
    ])
683
    |> validate_required([:name, :nickname])
684
685
686
687
688
689
690
691
    |> 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

692
  def register_changeset(struct, params \\ %{}, opts \\ []) do
feld's avatar
feld committed
693
694
    bio_limit = Config.get([:instance, :user_bio_length], 5000)
    name_limit = Config.get([:instance, :user_name_length], 100)
695
    reason_limit = Config.get([:instance, :registration_reason_length], 500)
696
    params = Map.put_new(params, :accepts_chat_messages, true)
697

698
699
700
    confirmed? =
      if is_nil(opts[:confirmed]) do
        !Config.get([:instance, :account_activation_required])
701
      else
702
        opts[:confirmed]
703
704
      end

705
706
707
    approved? =
      if is_nil(opts[:approved]) do
        !Config.get([:instance, :account_approval_required])
708
      else
709
        opts[:approved]
710
711
      end

minibikini's avatar
minibikini committed
712
    struct
713
    |> confirmation_changeset(set_confirmation: confirmed?)
714
    |> approval_changeset(set_approval: approved?)
715
716
717
718
719
720
721
722
    |> cast(params, [
      :bio,
      :raw_bio,
      :email,
      :name,
      :nickname,
      :password,
      :password_confirmation,
723
      :emoji,
724
725
      :accepts_chat_messages,
      :registration_reason
726
    ])
minibikini's avatar
minibikini committed
727
728
729
    |> validate_required([:name, :nickname, :password, :password_confirmation])
    |> validate_confirmation(:password)
    |> unique_constraint(:email)
730
731
732
733
734
735
736
737
    |> 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)

738
      if valid?, do: [], else: [email: "Invalid email"]
739
    end)
minibikini's avatar
minibikini committed
740
    |> unique_constraint(:nickname)
feld's avatar
feld committed
741
    |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
minibikini's avatar
minibikini committed
742
743
744
    |> validate_format(:nickname, local_nickname_regex())
    |> validate_length(:bio, max: bio_limit)
    |> validate_length(:name, min: 1, max: name_limit)
745
    |> validate_length(:registration_reason, max: reason_limit)
minibikini's avatar
minibikini committed
746
747
748
749
750
751
    |> 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
752

minibikini's avatar
minibikini committed
753
  def maybe_validate_required_email(changeset, true), do: changeset
754
755

  def maybe_validate_required_email(changeset, _) do
feld's avatar
feld committed
756
    if Config.get([:instance, :account_activation_required]) do
757
758
759
760
761
      validate_required(changeset, [:email])
    else
      changeset
    end
  end
lain's avatar
lain committed
762

minibikini's avatar
minibikini committed
763
764
765
766
  defp put_ap_id(changeset) do
    ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
    put_change(changeset, :ap_id, ap_id)
  end
767

minibikini's avatar
minibikini committed
768
769
  defp put_following_and_follower_address(changeset) do
    followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
lain's avatar
lain committed
770

minibikini's avatar
minibikini committed
771
772
    changeset
    |> put_change(:follower_address, followers)
lain's avatar
lain committed
773
774
  end

775
  defp autofollow_users(user) do
feld's avatar
feld committed
776
    candidates = Config.get([:instance, :autofollowed_nicknames])
777
778

    autofollowed_users =
779
      User.Query.build(%{nickname: candidates, local: true, is_active: true})
780
781
      |> Repo.all()

lain's avatar
lain committed
782
    follow_all(user, autofollowed_users)
783
784
  end

785
786
787
788
789
790
791
792
793
794
  defp autofollowing_users(user) do
    candidates = Config.get([:instance, :autofollowing_nicknames])

    User.Query.build(%{nickname: candidates, local: true, deactivated: false})
    |> Repo.all()
    |> Enum.each(&follow(&1, user, :follow_accept))

    {:ok, :success}
  end

795
796
  @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
  def register(%Ecto.Changeset{} = changeset) do
minibikini's avatar
minibikini committed
797
798
    with {:ok, user} <- Repo.insert(changeset) do
      post_register_action(user)
799
800
801
    end
  end

802
  def post_register_action(%User{is_confirmed: false} = user) do
feld's avatar
feld committed
803
    with {:ok, _} <- maybe_send_confirmation_email(user) do
804
805
806
807
      {:ok, user}
    end
  end

808
  def post_register_action(%User{is_approved: false} = user) do
809
810
811
812
813
814
    with {:ok, _} <- send_user_approval_email(user),
         {:ok, _} <- send_admin_approval_emails(user) do
      {:ok, user}
    end
  end

815
  def post_register_action(%User{is_approved: true, is_confirmed: true} = user) do
816
    with {:ok, user} <- autofollow_users(user),
817
         {:ok, _} <- autofollowing_users(user),
minibikini's avatar
minibikini committed
818
         {:ok, user} <- set_cache(user),
819
         {:ok, _} <- maybe_send_registration_email(user),
820
821
822
         {:ok, _} <- maybe_send_welcome_email(user),
         {:ok, _} <- maybe_send_welcome_message(user),
         {:ok, _} <- maybe_send_welcome_chat_message(user) do
823
824
825
826
      {:ok, user}
    end
  end

827
  defp send_user_approval_email(user) do
828
829
830
831
    user
    |> Pleroma.Emails.UserEmail.approval_pending_email()
    |> Pleroma.Emails.Mailer.deliver_async()

832
833
834
835
    {:ok, :enqueued}
  end

  defp send_admin_approval_emails(user) do
836
837
838
839
840
841
842
843
    all_superusers()
    |> Enum.filter(fn user -> not is_nil(user.email) end)
    |> Enum.each(fn superuser ->
      superuser
      |> Pleroma.Emails.AdminEmail.new_unapproved_registration(user)
      |> Pleroma.Emails.Mailer.deliver_async()
    end)

844
    {:ok, :enqueued}
845
846
  end

847
  defp maybe_send_welcome_message(user) do
Maksim's avatar
Maksim committed
848
849
850
851
852
853
854
855
    if User.WelcomeMessage.enabled?() do
      User.WelcomeMessage.post_message(user)
      {:ok, :enqueued}
    else
      {:ok, :noop}
    end
  end

856
  defp maybe_send_welcome_chat_message(user) do
Ilja's avatar
Ilja committed
857
858
859
860
861
862
863
864
    if User.WelcomeChatMessage.enabled?() do
      User.WelcomeChatMessage.post_message(user)
      {:ok, :enqueued}
    else
      {:ok, :noop}
    end
  end

865
  defp maybe_send_welcome_email(%User{email: email} = user) when is_binary(email) do
Maksim's avatar
Maksim committed
866
867
868
869
870
871
872
    if User.WelcomeEmail.enabled?() do
      User.WelcomeEmail.send_email(user)
      {:ok, :enqueued}
    else
      {:ok, :noop}
    end
  end
Maksim's avatar
Maksim committed
873

874
  defp maybe_send_welcome_email(_), do: {:ok, :noop}
875

feld's avatar
feld committed
876
877
  @spec maybe_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
  def maybe_send_confirmation_email(%User{is_confirmed: false, email: email} = user)
878
      when is_binary(email) do
879
880
    if Config.get([:instance, :account_activation_required]) do
      send_confirmation_email(user)
881
      {:ok, :enqueued}
882
883
884
885
886
    else
      {:ok, :noop}
    end
  end

feld's avatar
feld committed
887
  def maybe_send_confirmation_email(_), do: {:ok, :noop}
888
889
890
891
892
893
894
895

  @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
896
897
  end

898
899
900
901
902
903
904
  @spec maybe_send_registration_email(User.t()) :: {:ok, :enqueued | :noop}
  defp maybe_send_registration_email(%User{email: email} = user) when is_binary(email) do
    with false <- User.WelcomeEmail.enabled?(),
         false <- Config.get([:instance, :account_activation_required], false),
         false <- Config.get([:instance, :account_approval_required], false) do
      user
      |> Pleroma.Emails.UserEmail.successful_registration_email()
feld's avatar
feld committed
905
      |> Pleroma.Emails.Mailer.deliver_async()
906
907
908
909
910
911
912
913
914
915

      {:ok, :enqueued}
    else
      _ ->
        {:ok, :noop}
    end
  end

  defp maybe_send_registration_email(_), do: {:ok, :noop}

916
917
918
919
920
  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
921
    NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
922
923
924
925
  end

  def needs_update?(_), do: true

Maksim's avatar
Maksim committed
926
  @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
927
928

  # "Locked" (self-locked) users demand explicit authorization of follow requests
929
  def maybe_direct_follow(%User{} = follower, %User{local: true, is_locked: true} = followed) do
930
    follow(follower, followed, :follow_pending)
931
932
933
934
935
936
937
  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
938
    if not ap_enabled?(followed) do
939
      follow(follower, followed)
940
    else
minibikini's avatar
minibikini committed
941
      {:ok, follower, followed}
942
943
944
    end
  end

945
  @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
lain's avatar
lain committed
946
947
  @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
  def follow_all(follower, followeds) do
minibikini's avatar
minibikini committed
948
949
    followeds
    |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
950
    |> Enum.each(&follow(follower, &1, :follow_accept))
lain's avatar
lain committed
951

lain's avatar
lain committed
952
    set_cache(follower)
lain's avatar
lain committed
953
954
  end

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

958
    cond do
feld's avatar
feld committed
959
      not followed.is_active ->
960
        {:error, "Could not follow user: #{followed.nickname} is deactivated."}
lain's avatar
lain committed
961

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

965
      true ->
966
        FollowingRelationship.follow(follower, followed, state)
967
    end
lain's avatar
lain committed
968
  end
lain's avatar
lain committed
969

970
971
972
973
  def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
    {:error, "Not subscribed!"}
  end

974
  @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
lain's avatar
lain committed
975
  def unfollow(%User{} = follower, %User{} = followed) do
976
977
978
979
980
981
982
983
984
985
986
    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