Merge branch 'issue/2069' into 'develop'
[akkoma] / lib / pleroma / user.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.User do
6 use Ecto.Schema
7
8 import Ecto.Changeset
9 import Ecto.Query
10 import Ecto, only: [assoc: 2]
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
21 alias Pleroma.HTML
22 alias Pleroma.Keys
23 alias Pleroma.MFA
24 alias Pleroma.Notification
25 alias Pleroma.Object
26 alias Pleroma.Registration
27 alias Pleroma.Repo
28 alias Pleroma.User
29 alias Pleroma.UserRelationship
30 alias Pleroma.Web
31 alias Pleroma.Web.ActivityPub.ActivityPub
32 alias Pleroma.Web.ActivityPub.Builder
33 alias Pleroma.Web.ActivityPub.Pipeline
34 alias Pleroma.Web.ActivityPub.Utils
35 alias Pleroma.Web.CommonAPI
36 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
37 alias Pleroma.Web.OAuth
38 alias Pleroma.Web.RelMe
39 alias Pleroma.Workers.BackgroundWorker
40
41 require Logger
42
43 @type t :: %__MODULE__{}
44 @type account_status ::
45 :active
46 | :deactivated
47 | :password_reset_pending
48 | :confirmation_pending
49 | :approval_pending
50 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
51
52 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
53 @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])?)*$/
54
55 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
56 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
57
58 # AP ID user relationships (blocks, mutes etc.)
59 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
60 @user_relationships_config [
61 block: [
62 blocker_blocks: :blocked_users,
63 blockee_blocks: :blocker_users
64 ],
65 mute: [
66 muter_mutes: :muted_users,
67 mutee_mutes: :muter_users
68 ],
69 reblog_mute: [
70 reblog_muter_mutes: :reblog_muted_users,
71 reblog_mutee_mutes: :reblog_muter_users
72 ],
73 notification_mute: [
74 notification_muter_mutes: :notification_muted_users,
75 notification_mutee_mutes: :notification_muter_users
76 ],
77 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
78 inverse_subscription: [
79 subscribee_subscriptions: :subscriber_users,
80 subscriber_subscriptions: :subscribee_users
81 ]
82 ]
83
84 schema "users" do
85 field(:bio, :string, default: "")
86 field(:raw_bio, :string)
87 field(:email, :string)
88 field(:name, :string)
89 field(:nickname, :string)
90 field(:password_hash, :string)
91 field(:password, :string, virtual: true)
92 field(:password_confirmation, :string, virtual: true)
93 field(:keys, :string)
94 field(:public_key, :string)
95 field(:ap_id, :string)
96 field(:avatar, :map, default: %{})
97 field(:local, :boolean, default: true)
98 field(:follower_address, :string)
99 field(:following_address, :string)
100 field(:search_rank, :float, virtual: true)
101 field(:search_type, :integer, virtual: true)
102 field(:tags, {:array, :string}, default: [])
103 field(:last_refreshed_at, :naive_datetime_usec)
104 field(:last_digest_emailed_at, :naive_datetime)
105 field(:banner, :map, default: %{})
106 field(:background, :map, default: %{})
107 field(:note_count, :integer, default: 0)
108 field(:follower_count, :integer, default: 0)
109 field(:following_count, :integer, default: 0)
110 field(:is_locked, :boolean, default: false)
111 field(:confirmation_pending, :boolean, default: false)
112 field(:password_reset_pending, :boolean, default: false)
113 field(:approval_pending, :boolean, default: false)
114 field(:registration_reason, :string, default: nil)
115 field(:confirmation_token, :string, default: nil)
116 field(:default_scope, :string, default: "public")
117 field(:domain_blocks, {:array, :string}, default: [])
118 field(:deactivated, :boolean, default: false)
119 field(:no_rich_text, :boolean, default: false)
120 field(:ap_enabled, :boolean, default: false)
121 field(:is_moderator, :boolean, default: false)
122 field(:is_admin, :boolean, default: false)
123 field(:show_role, :boolean, default: true)
124 field(:mastofe_settings, :map, default: nil)
125 field(:uri, ObjectValidators.Uri, default: nil)
126 field(:hide_followers_count, :boolean, default: false)
127 field(:hide_follows_count, :boolean, default: false)
128 field(:hide_followers, :boolean, default: false)
129 field(:hide_follows, :boolean, default: false)
130 field(:hide_favorites, :boolean, default: true)
131 field(:pinned_activities, {:array, :string}, default: [])
132 field(:email_notifications, :map, default: %{"digest" => false})
133 field(:mascot, :map, default: nil)
134 field(:emoji, :map, default: %{})
135 field(:pleroma_settings_store, :map, default: %{})
136 field(:fields, {:array, :map}, default: [])
137 field(:raw_fields, {:array, :map}, default: [])
138 field(:is_discoverable, :boolean, default: false)
139 field(:invisible, :boolean, default: false)
140 field(:allow_following_move, :boolean, default: true)
141 field(:skip_thread_containment, :boolean, default: false)
142 field(:actor_type, :string, default: "Person")
143 field(:also_known_as, {:array, :string}, default: [])
144 field(:inbox, :string)
145 field(:shared_inbox, :string)
146 field(:accepts_chat_messages, :boolean, default: nil)
147
148 embeds_one(
149 :notification_settings,
150 Pleroma.User.NotificationSetting,
151 on_replace: :update
152 )
153
154 has_many(:notifications, Notification)
155 has_many(:registrations, Registration)
156 has_many(:deliveries, Delivery)
157
158 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
159 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
160
161 for {relationship_type,
162 [
163 {outgoing_relation, outgoing_relation_target},
164 {incoming_relation, incoming_relation_source}
165 ]} <- @user_relationships_config do
166 # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
167 # :notification_muter_mutes, :subscribee_subscriptions
168 has_many(outgoing_relation, UserRelationship,
169 foreign_key: :source_id,
170 where: [relationship_type: relationship_type]
171 )
172
173 # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
174 # :notification_mutee_mutes, :subscriber_subscriptions
175 has_many(incoming_relation, UserRelationship,
176 foreign_key: :target_id,
177 where: [relationship_type: relationship_type]
178 )
179
180 # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
181 # :notification_muted_users, :subscriber_users
182 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
183
184 # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
185 # :notification_muter_users, :subscribee_users
186 has_many(incoming_relation_source, through: [incoming_relation, :source])
187 end
188
189 # `:blocks` is deprecated (replaced with `blocked_users` relation)
190 field(:blocks, {:array, :string}, default: [])
191 # `:mutes` is deprecated (replaced with `muted_users` relation)
192 field(:mutes, {:array, :string}, default: [])
193 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
194 field(:muted_reblogs, {:array, :string}, default: [])
195 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
196 field(:muted_notifications, {:array, :string}, default: [])
197 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
198 field(:subscribers, {:array, :string}, default: [])
199
200 embeds_one(
201 :multi_factor_authentication_settings,
202 MFA.Settings,
203 on_replace: :delete
204 )
205
206 timestamps()
207 end
208
209 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
210 @user_relationships_config do
211 # `def blocked_users_relation/2`, `def muted_users_relation/2`,
212 # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
213 # `def subscriber_users/2`
214 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
215 target_users_query = assoc(user, unquote(outgoing_relation_target))
216
217 if restrict_deactivated? do
218 restrict_deactivated(target_users_query)
219 else
220 target_users_query
221 end
222 end
223
224 # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
225 # `def notification_muted_users/2`, `def subscriber_users/2`
226 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
227 __MODULE__
228 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
229 user,
230 restrict_deactivated?
231 ])
232 |> Repo.all()
233 end
234
235 # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
236 # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
237 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
238 __MODULE__
239 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
240 user,
241 restrict_deactivated?
242 ])
243 |> select([u], u.ap_id)
244 |> Repo.all()
245 end
246 end
247
248 defdelegate following_count(user), to: FollowingRelationship
249 defdelegate following(user), to: FollowingRelationship
250 defdelegate following?(follower, followed), to: FollowingRelationship
251 defdelegate following_ap_ids(user), to: FollowingRelationship
252 defdelegate get_follow_requests(user), to: FollowingRelationship
253 defdelegate search(query, opts \\ []), to: User.Search
254
255 @doc """
256 Dumps Flake Id to SQL-compatible format (16-byte UUID).
257 E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
258 """
259 def binary_id(source_id) when is_binary(source_id) do
260 with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
261 dumped_id
262 else
263 _ -> source_id
264 end
265 end
266
267 def binary_id(source_ids) when is_list(source_ids) do
268 Enum.map(source_ids, &binary_id/1)
269 end
270
271 def binary_id(%User{} = user), do: binary_id(user.id)
272
273 @doc "Returns status account"
274 @spec account_status(User.t()) :: account_status()
275 def account_status(%User{deactivated: true}), do: :deactivated
276 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
277 def account_status(%User{local: true, approval_pending: true}), do: :approval_pending
278
279 def account_status(%User{local: true, confirmation_pending: true}) do
280 if Config.get([:instance, :account_activation_required]) do
281 :confirmation_pending
282 else
283 :active
284 end
285 end
286
287 def account_status(%User{}), do: :active
288
289 @spec visible_for(User.t(), User.t() | nil) ::
290 :visible
291 | :invisible
292 | :restricted_unauthenticated
293 | :deactivated
294 | :confirmation_pending
295 def visible_for(user, for_user \\ nil)
296
297 def visible_for(%User{invisible: true}, _), do: :invisible
298
299 def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
300
301 def visible_for(%User{} = user, nil) do
302 if restrict_unauthenticated?(user) do
303 :restrict_unauthenticated
304 else
305 visible_account_status(user)
306 end
307 end
308
309 def visible_for(%User{} = user, for_user) do
310 if superuser?(for_user) do
311 :visible
312 else
313 visible_account_status(user)
314 end
315 end
316
317 def visible_for(_, _), do: :invisible
318
319 defp restrict_unauthenticated?(%User{local: true}) do
320 Config.restrict_unauthenticated_access?(:profiles, :local)
321 end
322
323 defp restrict_unauthenticated?(%User{local: _}) do
324 Config.restrict_unauthenticated_access?(:profiles, :remote)
325 end
326
327 defp visible_account_status(user) do
328 status = account_status(user)
329
330 if status in [:active, :password_reset_pending] do
331 :visible
332 else
333 status
334 end
335 end
336
337 @spec superuser?(User.t()) :: boolean()
338 def superuser?(%User{local: true, is_admin: true}), do: true
339 def superuser?(%User{local: true, is_moderator: true}), do: true
340 def superuser?(_), do: false
341
342 @spec invisible?(User.t()) :: boolean()
343 def invisible?(%User{invisible: true}), do: true
344 def invisible?(_), do: false
345
346 def avatar_url(user, options \\ []) do
347 case user.avatar do
348 %{"url" => [%{"href" => href} | _]} ->
349 href
350
351 _ ->
352 unless options[:no_default] do
353 Config.get([:assets, :default_user_avatar], "#{Web.base_url()}/images/avi.png")
354 end
355 end
356 end
357
358 def banner_url(user, options \\ []) do
359 case user.banner do
360 %{"url" => [%{"href" => href} | _]} -> href
361 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
362 end
363 end
364
365 # Should probably be renamed or removed
366 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
367
368 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
369 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
370
371 @spec ap_following(User.t()) :: String.t()
372 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
373 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
374
375 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
376 def restrict_deactivated(query) do
377 from(u in query, where: u.deactivated != ^true)
378 end
379
380 defp truncate_fields_param(params) do
381 if Map.has_key?(params, :fields) do
382 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
383 else
384 params
385 end
386 end
387
388 defp truncate_if_exists(params, key, max_length) do
389 if Map.has_key?(params, key) and is_binary(params[key]) do
390 {value, _chopped} = String.split_at(params[key], max_length)
391 Map.put(params, key, value)
392 else
393 params
394 end
395 end
396
397 defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
398
399 defp fix_follower_address(%{nickname: nickname} = params),
400 do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
401
402 defp fix_follower_address(params), do: params
403
404 def remote_user_changeset(struct \\ %User{local: false}, params) do
405 bio_limit = Config.get([:instance, :user_bio_length], 5000)
406 name_limit = Config.get([:instance, :user_name_length], 100)
407
408 name =
409 case params[:name] do
410 name when is_binary(name) and byte_size(name) > 0 -> name
411 _ -> params[:nickname]
412 end
413
414 params =
415 params
416 |> Map.put(:name, name)
417 |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
418 |> truncate_if_exists(:name, name_limit)
419 |> truncate_if_exists(:bio, bio_limit)
420 |> truncate_fields_param()
421 |> fix_follower_address()
422
423 struct
424 |> cast(
425 params,
426 [
427 :bio,
428 :emoji,
429 :ap_id,
430 :inbox,
431 :shared_inbox,
432 :nickname,
433 :public_key,
434 :avatar,
435 :ap_enabled,
436 :banner,
437 :is_locked,
438 :last_refreshed_at,
439 :uri,
440 :follower_address,
441 :following_address,
442 :hide_followers,
443 :hide_follows,
444 :hide_followers_count,
445 :hide_follows_count,
446 :follower_count,
447 :fields,
448 :following_count,
449 :is_discoverable,
450 :invisible,
451 :actor_type,
452 :also_known_as,
453 :accepts_chat_messages
454 ]
455 )
456 |> cast(params, [:name], empty_values: [])
457 |> validate_required([:ap_id])
458 |> validate_required([:name], trim: false)
459 |> unique_constraint(:nickname)
460 |> validate_format(:nickname, @email_regex)
461 |> validate_length(:bio, max: bio_limit)
462 |> validate_length(:name, max: name_limit)
463 |> validate_fields(true)
464 end
465
466 def update_changeset(struct, params \\ %{}) do
467 bio_limit = Config.get([:instance, :user_bio_length], 5000)
468 name_limit = Config.get([:instance, :user_name_length], 100)
469
470 struct
471 |> cast(
472 params,
473 [
474 :bio,
475 :raw_bio,
476 :name,
477 :emoji,
478 :avatar,
479 :public_key,
480 :inbox,
481 :shared_inbox,
482 :is_locked,
483 :no_rich_text,
484 :default_scope,
485 :banner,
486 :hide_follows,
487 :hide_followers,
488 :hide_followers_count,
489 :hide_follows_count,
490 :hide_favorites,
491 :allow_following_move,
492 :background,
493 :show_role,
494 :skip_thread_containment,
495 :fields,
496 :raw_fields,
497 :pleroma_settings_store,
498 :is_discoverable,
499 :actor_type,
500 :also_known_as,
501 :accepts_chat_messages
502 ]
503 )
504 |> unique_constraint(:nickname)
505 |> validate_format(:nickname, local_nickname_regex())
506 |> validate_length(:bio, max: bio_limit)
507 |> validate_length(:name, min: 1, max: name_limit)
508 |> validate_inclusion(:actor_type, ["Person", "Service"])
509 |> put_fields()
510 |> put_emoji()
511 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
512 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
513 |> put_change_if_present(:banner, &put_upload(&1, :banner))
514 |> put_change_if_present(:background, &put_upload(&1, :background))
515 |> put_change_if_present(
516 :pleroma_settings_store,
517 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
518 )
519 |> validate_fields(false)
520 end
521
522 defp put_fields(changeset) do
523 if raw_fields = get_change(changeset, :raw_fields) do
524 raw_fields =
525 raw_fields
526 |> Enum.filter(fn %{"name" => n} -> n != "" end)
527
528 fields =
529 raw_fields
530 |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
531
532 changeset
533 |> put_change(:raw_fields, raw_fields)
534 |> put_change(:fields, fields)
535 else
536 changeset
537 end
538 end
539
540 defp parse_fields(value) do
541 value
542 |> Formatter.linkify(mentions_format: :full)
543 |> elem(0)
544 end
545
546 defp put_emoji(changeset) do
547 emojified_fields = [:bio, :name, :raw_fields]
548
549 if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do
550 bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio))
551 name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name))
552
553 emoji = Map.merge(bio, name)
554
555 emoji =
556 changeset
557 |> get_field(:raw_fields)
558 |> Enum.reduce(emoji, fn x, acc ->
559 Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"]))
560 end)
561
562 put_change(changeset, :emoji, emoji)
563 else
564 changeset
565 end
566 end
567
568 defp put_change_if_present(changeset, map_field, value_function) do
569 with {:ok, value} <- fetch_change(changeset, map_field),
570 {:ok, new_value} <- value_function.(value) do
571 put_change(changeset, map_field, new_value)
572 else
573 _ -> changeset
574 end
575 end
576
577 defp put_upload(value, type) do
578 with %Plug.Upload{} <- value,
579 {:ok, object} <- ActivityPub.upload(value, type: type) do
580 {:ok, object.data}
581 end
582 end
583
584 def update_as_admin_changeset(struct, params) do
585 struct
586 |> update_changeset(params)
587 |> cast(params, [:email])
588 |> delete_change(:also_known_as)
589 |> unique_constraint(:email)
590 |> validate_format(:email, @email_regex)
591 |> validate_inclusion(:actor_type, ["Person", "Service"])
592 end
593
594 @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
595 def update_as_admin(user, params) do
596 params = Map.put(params, "password_confirmation", params["password"])
597 changeset = update_as_admin_changeset(user, params)
598
599 if params["password"] do
600 reset_password(user, changeset, params)
601 else
602 User.update_and_set_cache(changeset)
603 end
604 end
605
606 def password_update_changeset(struct, params) do
607 struct
608 |> cast(params, [:password, :password_confirmation])
609 |> validate_required([:password, :password_confirmation])
610 |> validate_confirmation(:password)
611 |> put_password_hash()
612 |> put_change(:password_reset_pending, false)
613 end
614
615 @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
616 def reset_password(%User{} = user, params) do
617 reset_password(user, user, params)
618 end
619
620 def reset_password(%User{id: user_id} = user, struct, params) do
621 multi =
622 Multi.new()
623 |> Multi.update(:user, password_update_changeset(struct, params))
624 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
625 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
626
627 case Repo.transaction(multi) do
628 {:ok, %{user: user} = _} -> set_cache(user)
629 {:error, _, changeset, _} -> {:error, changeset}
630 end
631 end
632
633 def update_password_reset_pending(user, value) do
634 user
635 |> change()
636 |> put_change(:password_reset_pending, value)
637 |> update_and_set_cache()
638 end
639
640 def force_password_reset_async(user) do
641 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
642 end
643
644 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
645 def force_password_reset(user), do: update_password_reset_pending(user, true)
646
647 # Used to auto-register LDAP accounts which won't have a password hash stored locally
648 def register_changeset_ldap(struct, params = %{password: password})
649 when is_nil(password) do
650 params = Map.put_new(params, :accepts_chat_messages, true)
651
652 params =
653 if Map.has_key?(params, :email) do
654 Map.put_new(params, :email, params[:email])
655 else
656 params
657 end
658
659 struct
660 |> cast(params, [
661 :name,
662 :nickname,
663 :email,
664 :accepts_chat_messages
665 ])
666 |> validate_required([:name, :nickname])
667 |> unique_constraint(:nickname)
668 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
669 |> validate_format(:nickname, local_nickname_regex())
670 |> put_ap_id()
671 |> unique_constraint(:ap_id)
672 |> put_following_and_follower_address()
673 end
674
675 def register_changeset(struct, params \\ %{}, opts \\ []) do
676 bio_limit = Config.get([:instance, :user_bio_length], 5000)
677 name_limit = Config.get([:instance, :user_name_length], 100)
678 reason_limit = Config.get([:instance, :registration_reason_length], 500)
679 params = Map.put_new(params, :accepts_chat_messages, true)
680
681 need_confirmation? =
682 if is_nil(opts[:need_confirmation]) do
683 Config.get([:instance, :account_activation_required])
684 else
685 opts[:need_confirmation]
686 end
687
688 need_approval? =
689 if is_nil(opts[:need_approval]) do
690 Config.get([:instance, :account_approval_required])
691 else
692 opts[:need_approval]
693 end
694
695 struct
696 |> confirmation_changeset(need_confirmation: need_confirmation?)
697 |> approval_changeset(need_approval: need_approval?)
698 |> cast(params, [
699 :bio,
700 :raw_bio,
701 :email,
702 :name,
703 :nickname,
704 :password,
705 :password_confirmation,
706 :emoji,
707 :accepts_chat_messages,
708 :registration_reason
709 ])
710 |> validate_required([:name, :nickname, :password, :password_confirmation])
711 |> validate_confirmation(:password)
712 |> unique_constraint(:email)
713 |> validate_format(:email, @email_regex)
714 |> validate_change(:email, fn :email, email ->
715 valid? =
716 Config.get([User, :email_blacklist])
717 |> Enum.all?(fn blacklisted_domain ->
718 !String.ends_with?(email, ["@" <> blacklisted_domain, "." <> blacklisted_domain])
719 end)
720
721 if valid?, do: [], else: [email: "Invalid email"]
722 end)
723 |> unique_constraint(:nickname)
724 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
725 |> validate_format(:nickname, local_nickname_regex())
726 |> validate_length(:bio, max: bio_limit)
727 |> validate_length(:name, min: 1, max: name_limit)
728 |> validate_length(:registration_reason, max: reason_limit)
729 |> maybe_validate_required_email(opts[:external])
730 |> put_password_hash
731 |> put_ap_id()
732 |> unique_constraint(:ap_id)
733 |> put_following_and_follower_address()
734 end
735
736 def maybe_validate_required_email(changeset, true), do: changeset
737
738 def maybe_validate_required_email(changeset, _) do
739 if Config.get([:instance, :account_activation_required]) do
740 validate_required(changeset, [:email])
741 else
742 changeset
743 end
744 end
745
746 defp put_ap_id(changeset) do
747 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
748 put_change(changeset, :ap_id, ap_id)
749 end
750
751 defp put_following_and_follower_address(changeset) do
752 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
753
754 changeset
755 |> put_change(:follower_address, followers)
756 end
757
758 defp autofollow_users(user) do
759 candidates = Config.get([:instance, :autofollowed_nicknames])
760
761 autofollowed_users =
762 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
763 |> Repo.all()
764
765 follow_all(user, autofollowed_users)
766 end
767
768 defp autofollowing_users(user) do
769 candidates = Config.get([:instance, :autofollowing_nicknames])
770
771 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
772 |> Repo.all()
773 |> Enum.each(&follow(&1, user, :follow_accept))
774
775 {:ok, :success}
776 end
777
778 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
779 def register(%Ecto.Changeset{} = changeset) do
780 with {:ok, user} <- Repo.insert(changeset) do
781 post_register_action(user)
782 end
783 end
784
785 def post_register_action(%User{} = user) do
786 with {:ok, user} <- autofollow_users(user),
787 {:ok, _} <- autofollowing_users(user),
788 {:ok, user} <- set_cache(user),
789 {:ok, _} <- send_welcome_email(user),
790 {:ok, _} <- send_welcome_message(user),
791 {:ok, _} <- send_welcome_chat_message(user),
792 {:ok, _} <- try_send_confirmation_email(user) do
793 {:ok, user}
794 end
795 end
796
797 def send_welcome_message(user) do
798 if User.WelcomeMessage.enabled?() do
799 User.WelcomeMessage.post_message(user)
800 {:ok, :enqueued}
801 else
802 {:ok, :noop}
803 end
804 end
805
806 def send_welcome_chat_message(user) do
807 if User.WelcomeChatMessage.enabled?() do
808 User.WelcomeChatMessage.post_message(user)
809 {:ok, :enqueued}
810 else
811 {:ok, :noop}
812 end
813 end
814
815 def send_welcome_email(%User{email: email} = user) when is_binary(email) do
816 if User.WelcomeEmail.enabled?() do
817 User.WelcomeEmail.send_email(user)
818 {:ok, :enqueued}
819 else
820 {:ok, :noop}
821 end
822 end
823
824 def send_welcome_email(_), do: {:ok, :noop}
825
826 @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
827 def try_send_confirmation_email(%User{confirmation_pending: true, email: email} = user)
828 when is_binary(email) do
829 if Config.get([:instance, :account_activation_required]) do
830 send_confirmation_email(user)
831 {:ok, :enqueued}
832 else
833 {:ok, :noop}
834 end
835 end
836
837 def try_send_confirmation_email(_), do: {:ok, :noop}
838
839 @spec send_confirmation_email(Uset.t()) :: User.t()
840 def send_confirmation_email(%User{} = user) do
841 user
842 |> Pleroma.Emails.UserEmail.account_confirmation_email()
843 |> Pleroma.Emails.Mailer.deliver_async()
844
845 user
846 end
847
848 def needs_update?(%User{local: true}), do: false
849
850 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
851
852 def needs_update?(%User{local: false} = user) do
853 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
854 end
855
856 def needs_update?(_), do: true
857
858 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
859
860 # "Locked" (self-locked) users demand explicit authorization of follow requests
861 def maybe_direct_follow(%User{} = follower, %User{local: true, is_locked: true} = followed) do
862 follow(follower, followed, :follow_pending)
863 end
864
865 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
866 follow(follower, followed)
867 end
868
869 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
870 if not ap_enabled?(followed) do
871 follow(follower, followed)
872 else
873 {:ok, follower}
874 end
875 end
876
877 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
878 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
879 def follow_all(follower, followeds) do
880 followeds
881 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
882 |> Enum.each(&follow(follower, &1, :follow_accept))
883
884 set_cache(follower)
885 end
886
887 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
888 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
889
890 cond do
891 followed.deactivated ->
892 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
893
894 deny_follow_blocked and blocks?(followed, follower) ->
895 {:error, "Could not follow user: #{followed.nickname} blocked you."}
896
897 true ->
898 FollowingRelationship.follow(follower, followed, state)
899
900 {:ok, _} = update_follower_count(followed)
901
902 follower
903 |> update_following_count()
904 end
905 end
906
907 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
908 {:error, "Not subscribed!"}
909 end
910
911 @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
912 def unfollow(%User{} = follower, %User{} = followed) do
913 case do_unfollow(follower, followed) do
914 {:ok, follower, followed} ->
915 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
916
917 error ->
918 error
919 end
920 end
921
922 @spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()}
923 defp do_unfollow(%User{} = follower, %User{} = followed) do
924 case get_follow_state(follower, followed) do
925 state when state in [:follow_pending, :follow_accept] ->
926 FollowingRelationship.unfollow(follower, followed)
927 {:ok, followed} = update_follower_count(followed)
928
929 {:ok, follower} = update_following_count(follower)
930
931 {:ok, follower, followed}
932
933 nil ->
934 {:error, "Not subscribed!"}
935 end
936 end
937
938 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
939 def get_follow_state(%User{} = follower, %User{} = following) do
940 following_relationship = FollowingRelationship.get(follower, following)
941 get_follow_state(follower, following, following_relationship)
942 end
943
944 def get_follow_state(
945 %User{} = follower,
946 %User{} = following,
947 following_relationship
948 ) do
949 case {following_relationship, following.local} do
950 {nil, false} ->
951 case Utils.fetch_latest_follow(follower, following) do
952 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
953 FollowingRelationship.state_to_enum(state)
954
955 _ ->
956 nil
957 end
958
959 {%{state: state}, _} ->
960 state
961
962 {nil, _} ->
963 nil
964 end
965 end
966
967 def locked?(%User{} = user) do
968 user.is_locked || false
969 end
970
971 def get_by_id(id) do
972 Repo.get_by(User, id: id)
973 end
974
975 def get_by_ap_id(ap_id) do
976 Repo.get_by(User, ap_id: ap_id)
977 end
978
979 def get_all_by_ap_id(ap_ids) do
980 from(u in __MODULE__,
981 where: u.ap_id in ^ap_ids
982 )
983 |> Repo.all()
984 end
985
986 def get_all_by_ids(ids) do
987 from(u in __MODULE__, where: u.id in ^ids)
988 |> Repo.all()
989 end
990
991 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
992 # of the ap_id and the domain and tries to get that user
993 def get_by_guessed_nickname(ap_id) do
994 domain = URI.parse(ap_id).host
995 name = List.last(String.split(ap_id, "/"))
996 nickname = "#{name}@#{domain}"
997
998 get_cached_by_nickname(nickname)
999 end
1000
1001 def set_cache({:ok, user}), do: set_cache(user)
1002 def set_cache({:error, err}), do: {:error, err}
1003
1004 def set_cache(%User{} = user) do
1005 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
1006 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
1007 Cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
1008 {:ok, user}
1009 end
1010
1011 def update_and_set_cache(struct, params) do
1012 struct
1013 |> update_changeset(params)
1014 |> update_and_set_cache()
1015 end
1016
1017 def update_and_set_cache(changeset) do
1018 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
1019 set_cache(user)
1020 end
1021 end
1022
1023 def get_user_friends_ap_ids(user) do
1024 from(u in User.get_friends_query(user), select: u.ap_id)
1025 |> Repo.all()
1026 end
1027
1028 @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
1029 def get_cached_user_friends_ap_ids(user) do
1030 Cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
1031 get_user_friends_ap_ids(user)
1032 end)
1033 end
1034
1035 def invalidate_cache(user) do
1036 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
1037 Cachex.del(:user_cache, "nickname:#{user.nickname}")
1038 Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
1039 end
1040
1041 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
1042 def get_cached_by_ap_id(ap_id) do
1043 key = "ap_id:#{ap_id}"
1044
1045 with {:ok, nil} <- Cachex.get(:user_cache, key),
1046 user when not is_nil(user) <- get_by_ap_id(ap_id),
1047 {:ok, true} <- Cachex.put(:user_cache, key, user) do
1048 user
1049 else
1050 {:ok, user} -> user
1051 nil -> nil
1052 end
1053 end
1054
1055 def get_cached_by_id(id) do
1056 key = "id:#{id}"
1057
1058 ap_id =
1059 Cachex.fetch!(:user_cache, key, fn _ ->
1060 user = get_by_id(id)
1061
1062 if user do
1063 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
1064 {:commit, user.ap_id}
1065 else
1066 {:ignore, ""}
1067 end
1068 end)
1069
1070 get_cached_by_ap_id(ap_id)
1071 end
1072
1073 def get_cached_by_nickname(nickname) do
1074 key = "nickname:#{nickname}"
1075
1076 Cachex.fetch!(:user_cache, key, fn ->
1077 case get_or_fetch_by_nickname(nickname) do
1078 {:ok, user} -> {:commit, user}
1079 {:error, _error} -> {:ignore, nil}
1080 end
1081 end)
1082 end
1083
1084 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
1085 restrict_to_local = Config.get([:instance, :limit_to_local_content])
1086
1087 cond do
1088 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
1089 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
1090
1091 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
1092 get_cached_by_nickname(nickname_or_id)
1093
1094 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
1095 get_cached_by_nickname(nickname_or_id)
1096
1097 true ->
1098 nil
1099 end
1100 end
1101
1102 @spec get_by_nickname(String.t()) :: User.t() | nil
1103 def get_by_nickname(nickname) do
1104 Repo.get_by(User, nickname: nickname) ||
1105 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
1106 Repo.get_by(User, nickname: local_nickname(nickname))
1107 end
1108 end
1109
1110 def get_by_email(email), do: Repo.get_by(User, email: email)
1111
1112 def get_by_nickname_or_email(nickname_or_email) do
1113 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
1114 end
1115
1116 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
1117
1118 def get_or_fetch_by_nickname(nickname) do
1119 with %User{} = user <- get_by_nickname(nickname) do
1120 {:ok, user}
1121 else
1122 _e ->
1123 with [_nick, _domain] <- String.split(nickname, "@"),
1124 {:ok, user} <- fetch_by_nickname(nickname) do
1125 {:ok, user}
1126 else
1127 _e -> {:error, "not found " <> nickname}
1128 end
1129 end
1130 end
1131
1132 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1133 def get_followers_query(%User{} = user, nil) do
1134 User.Query.build(%{followers: user, deactivated: false})
1135 end
1136
1137 def get_followers_query(%User{} = user, page) do
1138 user
1139 |> get_followers_query(nil)
1140 |> User.Query.paginate(page, 20)
1141 end
1142
1143 @spec get_followers_query(User.t()) :: Ecto.Query.t()
1144 def get_followers_query(%User{} = user), do: get_followers_query(user, nil)
1145
1146 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1147 def get_followers(%User{} = user, page \\ nil) do
1148 user
1149 |> get_followers_query(page)
1150 |> Repo.all()
1151 end
1152
1153 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1154 def get_external_followers(%User{} = user, page \\ nil) do
1155 user
1156 |> get_followers_query(page)
1157 |> User.Query.build(%{external: true})
1158 |> Repo.all()
1159 end
1160
1161 def get_followers_ids(%User{} = user, page \\ nil) do
1162 user
1163 |> get_followers_query(page)
1164 |> select([u], u.id)
1165 |> Repo.all()
1166 end
1167
1168 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1169 def get_friends_query(%User{} = user, nil) do
1170 User.Query.build(%{friends: user, deactivated: false})
1171 end
1172
1173 def get_friends_query(%User{} = user, page) do
1174 user
1175 |> get_friends_query(nil)
1176 |> User.Query.paginate(page, 20)
1177 end
1178
1179 @spec get_friends_query(User.t()) :: Ecto.Query.t()
1180 def get_friends_query(%User{} = user), do: get_friends_query(user, nil)
1181
1182 def get_friends(%User{} = user, page \\ nil) do
1183 user
1184 |> get_friends_query(page)
1185 |> Repo.all()
1186 end
1187
1188 def get_friends_ap_ids(%User{} = user) do
1189 user
1190 |> get_friends_query(nil)
1191 |> select([u], u.ap_id)
1192 |> Repo.all()
1193 end
1194
1195 def get_friends_ids(%User{} = user, page \\ nil) do
1196 user
1197 |> get_friends_query(page)
1198 |> select([u], u.id)
1199 |> Repo.all()
1200 end
1201
1202 def increase_note_count(%User{} = user) do
1203 User
1204 |> where(id: ^user.id)
1205 |> update([u], inc: [note_count: 1])
1206 |> select([u], u)
1207 |> Repo.update_all([])
1208 |> case do
1209 {1, [user]} -> set_cache(user)
1210 _ -> {:error, user}
1211 end
1212 end
1213
1214 def decrease_note_count(%User{} = user) do
1215 User
1216 |> where(id: ^user.id)
1217 |> update([u],
1218 set: [
1219 note_count: fragment("greatest(0, note_count - 1)")
1220 ]
1221 )
1222 |> select([u], u)
1223 |> Repo.update_all([])
1224 |> case do
1225 {1, [user]} -> set_cache(user)
1226 _ -> {:error, user}
1227 end
1228 end
1229
1230 def update_note_count(%User{} = user, note_count \\ nil) do
1231 note_count =
1232 note_count ||
1233 from(
1234 a in Object,
1235 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1236 select: count(a.id)
1237 )
1238 |> Repo.one()
1239
1240 user
1241 |> cast(%{note_count: note_count}, [:note_count])
1242 |> update_and_set_cache()
1243 end
1244
1245 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1246 def maybe_fetch_follow_information(user) do
1247 with {:ok, user} <- fetch_follow_information(user) do
1248 user
1249 else
1250 e ->
1251 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1252
1253 user
1254 end
1255 end
1256
1257 def fetch_follow_information(user) do
1258 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1259 user
1260 |> follow_information_changeset(info)
1261 |> update_and_set_cache()
1262 end
1263 end
1264
1265 defp follow_information_changeset(user, params) do
1266 user
1267 |> cast(params, [
1268 :hide_followers,
1269 :hide_follows,
1270 :follower_count,
1271 :following_count,
1272 :hide_followers_count,
1273 :hide_follows_count
1274 ])
1275 end
1276
1277 @spec update_follower_count(User.t()) :: {:ok, User.t()}
1278 def update_follower_count(%User{} = user) do
1279 if user.local or !Config.get([:instance, :external_user_synchronization]) do
1280 follower_count = FollowingRelationship.follower_count(user)
1281
1282 user
1283 |> follow_information_changeset(%{follower_count: follower_count})
1284 |> update_and_set_cache
1285 else
1286 {:ok, maybe_fetch_follow_information(user)}
1287 end
1288 end
1289
1290 @spec update_following_count(User.t()) :: {:ok, User.t()}
1291 def update_following_count(%User{local: false} = user) do
1292 if Config.get([:instance, :external_user_synchronization]) do
1293 {:ok, maybe_fetch_follow_information(user)}
1294 else
1295 {:ok, user}
1296 end
1297 end
1298
1299 def update_following_count(%User{local: true} = user) do
1300 following_count = FollowingRelationship.following_count(user)
1301
1302 user
1303 |> follow_information_changeset(%{following_count: following_count})
1304 |> update_and_set_cache()
1305 end
1306
1307 @spec get_users_from_set([String.t()], keyword()) :: [User.t()]
1308 def get_users_from_set(ap_ids, opts \\ []) do
1309 local_only = Keyword.get(opts, :local_only, true)
1310 criteria = %{ap_id: ap_ids, deactivated: false}
1311 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1312
1313 User.Query.build(criteria)
1314 |> Repo.all()
1315 end
1316
1317 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1318 def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
1319 to = [actor | to]
1320
1321 query = User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1322
1323 query
1324 |> Repo.all()
1325 end
1326
1327 @spec mute(User.t(), User.t(), boolean()) ::
1328 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1329 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1330 add_to_mutes(muter, mutee, notifications?)
1331 end
1332
1333 def unmute(%User{} = muter, %User{} = mutee) do
1334 remove_from_mutes(muter, mutee)
1335 end
1336
1337 def subscribe(%User{} = subscriber, %User{} = target) do
1338 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
1339
1340 if blocks?(target, subscriber) and deny_follow_blocked do
1341 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1342 else
1343 # Note: the relationship is inverse: subscriber acts as relationship target
1344 UserRelationship.create_inverse_subscription(target, subscriber)
1345 end
1346 end
1347
1348 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1349 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1350 subscribe(subscriber, subscribee)
1351 end
1352 end
1353
1354 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1355 # Note: the relationship is inverse: subscriber acts as relationship target
1356 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1357 end
1358
1359 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1360 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1361 unsubscribe(unsubscriber, user)
1362 end
1363 end
1364
1365 def block(%User{} = blocker, %User{} = blocked) do
1366 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1367 blocker =
1368 if following?(blocker, blocked) do
1369 {:ok, blocker, _} = unfollow(blocker, blocked)
1370 blocker
1371 else
1372 blocker
1373 end
1374
1375 # clear any requested follows as well
1376 blocked =
1377 case CommonAPI.reject_follow_request(blocked, blocker) do
1378 {:ok, %User{} = updated_blocked} -> updated_blocked
1379 nil -> blocked
1380 end
1381
1382 unsubscribe(blocked, blocker)
1383
1384 unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
1385 if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker)
1386
1387 {:ok, blocker} = update_follower_count(blocker)
1388 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1389 add_to_block(blocker, blocked)
1390 end
1391
1392 # helper to handle the block given only an actor's AP id
1393 def block(%User{} = blocker, %{ap_id: ap_id}) do
1394 block(blocker, get_cached_by_ap_id(ap_id))
1395 end
1396
1397 def unblock(%User{} = blocker, %User{} = blocked) do
1398 remove_from_block(blocker, blocked)
1399 end
1400
1401 # helper to handle the block given only an actor's AP id
1402 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1403 unblock(blocker, get_cached_by_ap_id(ap_id))
1404 end
1405
1406 def mutes?(nil, _), do: false
1407 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1408
1409 def mutes_user?(%User{} = user, %User{} = target) do
1410 UserRelationship.mute_exists?(user, target)
1411 end
1412
1413 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1414 def muted_notifications?(nil, _), do: false
1415
1416 def muted_notifications?(%User{} = user, %User{} = target),
1417 do: UserRelationship.notification_mute_exists?(user, target)
1418
1419 def blocks?(nil, _), do: false
1420
1421 def blocks?(%User{} = user, %User{} = target) do
1422 blocks_user?(user, target) ||
1423 (blocks_domain?(user, target) and not User.following?(user, target))
1424 end
1425
1426 def blocks_user?(%User{} = user, %User{} = target) do
1427 UserRelationship.block_exists?(user, target)
1428 end
1429
1430 def blocks_user?(_, _), do: false
1431
1432 def blocks_domain?(%User{} = user, %User{} = target) do
1433 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1434 %{host: host} = URI.parse(target.ap_id)
1435 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1436 end
1437
1438 def blocks_domain?(_, _), do: false
1439
1440 def subscribed_to?(%User{} = user, %User{} = target) do
1441 # Note: the relationship is inverse: subscriber acts as relationship target
1442 UserRelationship.inverse_subscription_exists?(target, user)
1443 end
1444
1445 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1446 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1447 subscribed_to?(user, target)
1448 end
1449 end
1450
1451 @doc """
1452 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1453 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1454 """
1455 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1456 def outgoing_relationships_ap_ids(_user, []), do: %{}
1457
1458 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1459
1460 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1461 when is_list(relationship_types) do
1462 db_result =
1463 user
1464 |> assoc(:outgoing_relationships)
1465 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1466 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1467 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1468 |> group_by([user_rel, u], user_rel.relationship_type)
1469 |> Repo.all()
1470 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1471
1472 Enum.into(
1473 relationship_types,
1474 %{},
1475 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1476 )
1477 end
1478
1479 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1480
1481 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1482
1483 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1484
1485 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1486 when is_list(relationship_types) do
1487 user
1488 |> assoc(:incoming_relationships)
1489 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1490 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1491 |> maybe_filter_on_ap_id(ap_ids)
1492 |> select([user_rel, u], u.ap_id)
1493 |> distinct(true)
1494 |> Repo.all()
1495 end
1496
1497 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1498 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1499 end
1500
1501 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1502
1503 def deactivate_async(user, status \\ true) do
1504 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1505 end
1506
1507 def deactivate(user, status \\ true)
1508
1509 def deactivate(users, status) when is_list(users) do
1510 Repo.transaction(fn ->
1511 for user <- users, do: deactivate(user, status)
1512 end)
1513 end
1514
1515 def deactivate(%User{} = user, status) do
1516 with {:ok, user} <- set_activation_status(user, status) do
1517 user
1518 |> get_followers()
1519 |> Enum.filter(& &1.local)
1520 |> Enum.each(&set_cache(update_following_count(&1)))
1521
1522 # Only update local user counts, remote will be update during the next pull.
1523 user
1524 |> get_friends()
1525 |> Enum.filter(& &1.local)
1526 |> Enum.each(&do_unfollow(user, &1))
1527
1528 {:ok, user}
1529 end
1530 end
1531
1532 def approve(users) when is_list(users) do
1533 Repo.transaction(fn ->
1534 Enum.map(users, fn user ->
1535 with {:ok, user} <- approve(user), do: user
1536 end)
1537 end)
1538 end
1539
1540 def approve(%User{} = user) do
1541 change(user, approval_pending: false)
1542 |> update_and_set_cache()
1543 end
1544
1545 def update_notification_settings(%User{} = user, settings) do
1546 user
1547 |> cast(%{notification_settings: settings}, [])
1548 |> cast_embed(:notification_settings)
1549 |> validate_required([:notification_settings])
1550 |> update_and_set_cache()
1551 end
1552
1553 @spec purge_user_changeset(User.t()) :: Changeset.t()
1554 def purge_user_changeset(user) do
1555 # "Right to be forgotten"
1556 # https://gdpr.eu/right-to-be-forgotten/
1557 change(user, %{
1558 bio: "",
1559 raw_bio: nil,
1560 email: nil,
1561 name: nil,
1562 password_hash: nil,
1563 keys: nil,
1564 public_key: nil,
1565 avatar: %{},
1566 tags: [],
1567 last_refreshed_at: nil,
1568 last_digest_emailed_at: nil,
1569 banner: %{},
1570 background: %{},
1571 note_count: 0,
1572 follower_count: 0,
1573 following_count: 0,
1574 is_locked: false,
1575 confirmation_pending: false,
1576 password_reset_pending: false,
1577 approval_pending: false,
1578 registration_reason: nil,
1579 confirmation_token: nil,
1580 domain_blocks: [],
1581 deactivated: true,
1582 ap_enabled: false,
1583 is_moderator: false,
1584 is_admin: false,
1585 mastofe_settings: nil,
1586 mascot: nil,
1587 emoji: %{},
1588 pleroma_settings_store: %{},
1589 fields: [],
1590 raw_fields: [],
1591 is_discoverable: false,
1592 also_known_as: []
1593 })
1594 end
1595
1596 def delete(users) when is_list(users) do
1597 for user <- users, do: delete(user)
1598 end
1599
1600 def delete(%User{} = user) do
1601 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1602 end
1603
1604 defp delete_and_invalidate_cache(%User{} = user) do
1605 invalidate_cache(user)
1606 Repo.delete(user)
1607 end
1608
1609 defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user)
1610
1611 defp delete_or_deactivate(%User{local: true} = user) do
1612 status = account_status(user)
1613
1614 case status do
1615 :confirmation_pending ->
1616 delete_and_invalidate_cache(user)
1617
1618 :approval_pending ->
1619 delete_and_invalidate_cache(user)
1620
1621 _ ->
1622 user
1623 |> purge_user_changeset()
1624 |> update_and_set_cache()
1625 end
1626 end
1627
1628 def perform(:force_password_reset, user), do: force_password_reset(user)
1629
1630 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1631 def perform(:delete, %User{} = user) do
1632 # Remove all relationships
1633 user
1634 |> get_followers()
1635 |> Enum.each(fn follower ->
1636 ActivityPub.unfollow(follower, user)
1637 unfollow(follower, user)
1638 end)
1639
1640 user
1641 |> get_friends()
1642 |> Enum.each(fn followed ->
1643 ActivityPub.unfollow(user, followed)
1644 unfollow(user, followed)
1645 end)
1646
1647 delete_user_activities(user)
1648 delete_notifications_from_user_activities(user)
1649
1650 delete_outgoing_pending_follow_requests(user)
1651
1652 delete_or_deactivate(user)
1653 end
1654
1655 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1656
1657 @spec external_users_query() :: Ecto.Query.t()
1658 def external_users_query do
1659 User.Query.build(%{
1660 external: true,
1661 active: true,
1662 order_by: :id
1663 })
1664 end
1665
1666 @spec external_users(keyword()) :: [User.t()]
1667 def external_users(opts \\ []) do
1668 query =
1669 external_users_query()
1670 |> select([u], struct(u, [:id, :ap_id]))
1671
1672 query =
1673 if opts[:max_id],
1674 do: where(query, [u], u.id > ^opts[:max_id]),
1675 else: query
1676
1677 query =
1678 if opts[:limit],
1679 do: limit(query, ^opts[:limit]),
1680 else: query
1681
1682 Repo.all(query)
1683 end
1684
1685 def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
1686 Notification
1687 |> join(:inner, [n], activity in assoc(n, :activity))
1688 |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
1689 |> Repo.delete_all()
1690 end
1691
1692 def delete_user_activities(%User{ap_id: ap_id} = user) do
1693 ap_id
1694 |> Activity.Queries.by_actor()
1695 |> Repo.chunk_stream(50, :batches)
1696 |> Stream.each(fn activities ->
1697 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
1698 end)
1699 |> Stream.run()
1700 end
1701
1702 defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
1703 with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
1704 {:ok, delete_data, _} <- Builder.delete(user, object) do
1705 Pipeline.common_pipeline(delete_data, local: user.local)
1706 else
1707 {:find_object, nil} ->
1708 # We have the create activity, but not the object, it was probably pruned.
1709 # Insert a tombstone and try again
1710 with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
1711 {:ok, _tombstone} <- Object.create(tombstone_data) do
1712 delete_activity(activity, user)
1713 end
1714
1715 e ->
1716 Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
1717 Logger.error("Error: #{inspect(e)}")
1718 end
1719 end
1720
1721 defp delete_activity(%{data: %{"type" => type}} = activity, user)
1722 when type in ["Like", "Announce"] do
1723 {:ok, undo, _} = Builder.undo(user, activity)
1724 Pipeline.common_pipeline(undo, local: user.local)
1725 end
1726
1727 defp delete_activity(_activity, _user), do: "Doing nothing"
1728
1729 defp delete_outgoing_pending_follow_requests(user) do
1730 user
1731 |> FollowingRelationship.outgoing_pending_follow_requests_query()
1732 |> Repo.delete_all()
1733 end
1734
1735 def html_filter_policy(%User{no_rich_text: true}) do
1736 Pleroma.HTML.Scrubber.TwitterText
1737 end
1738
1739 def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
1740
1741 def fetch_by_ap_id(ap_id, opts \\ []), do: ActivityPub.make_user_from_ap_id(ap_id, opts)
1742
1743 def get_or_fetch_by_ap_id(ap_id, opts \\ []) do
1744 cached_user = get_cached_by_ap_id(ap_id)
1745
1746 maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id, opts)
1747
1748 case {cached_user, maybe_fetched_user} do
1749 {_, {:ok, %User{} = user}} ->
1750 {:ok, user}
1751
1752 {%User{} = user, _} ->
1753 {:ok, user}
1754
1755 _ ->
1756 {:error, :not_found}
1757 end
1758 end
1759
1760 @doc """
1761 Creates an internal service actor by URI if missing.
1762 Optionally takes nickname for addressing.
1763 """
1764 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1765 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1766 {_, user} =
1767 case get_cached_by_ap_id(uri) do
1768 nil ->
1769 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1770 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1771 {:error, nil}
1772 end
1773
1774 %User{invisible: false} = user ->
1775 set_invisible(user)
1776
1777 user ->
1778 {:ok, user}
1779 end
1780
1781 user
1782 end
1783
1784 @spec set_invisible(User.t()) :: {:ok, User.t()}
1785 defp set_invisible(user) do
1786 user
1787 |> change(%{invisible: true})
1788 |> update_and_set_cache()
1789 end
1790
1791 @spec create_service_actor(String.t(), String.t()) ::
1792 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1793 defp create_service_actor(uri, nickname) do
1794 %User{
1795 invisible: true,
1796 local: true,
1797 ap_id: uri,
1798 nickname: nickname,
1799 follower_address: uri <> "/followers"
1800 }
1801 |> change
1802 |> unique_constraint(:nickname)
1803 |> Repo.insert()
1804 |> set_cache()
1805 end
1806
1807 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1808 key =
1809 public_key_pem
1810 |> :public_key.pem_decode()
1811 |> hd()
1812 |> :public_key.pem_entry_decode()
1813
1814 {:ok, key}
1815 end
1816
1817 def public_key(_), do: {:error, "key not found"}
1818
1819 def get_public_key_for_ap_id(ap_id, opts \\ []) do
1820 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id, opts),
1821 {:ok, public_key} <- public_key(user) do
1822 {:ok, public_key}
1823 else
1824 _ -> :error
1825 end
1826 end
1827
1828 def ap_enabled?(%User{local: true}), do: true
1829 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1830 def ap_enabled?(_), do: false
1831
1832 @doc "Gets or fetch a user by uri or nickname."
1833 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1834 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1835 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1836
1837 # wait a period of time and return newest version of the User structs
1838 # this is because we have synchronous follow APIs and need to simulate them
1839 # with an async handshake
1840 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1841 with %User{} = a <- get_cached_by_id(a.id),
1842 %User{} = b <- get_cached_by_id(b.id) do
1843 {:ok, a, b}
1844 else
1845 nil -> :error
1846 end
1847 end
1848
1849 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1850 with :ok <- :timer.sleep(timeout),
1851 %User{} = a <- get_cached_by_id(a.id),
1852 %User{} = b <- get_cached_by_id(b.id) do
1853 {:ok, a, b}
1854 else
1855 nil -> :error
1856 end
1857 end
1858
1859 def parse_bio(bio) when is_binary(bio) and bio != "" do
1860 bio
1861 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1862 |> elem(0)
1863 end
1864
1865 def parse_bio(_), do: ""
1866
1867 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1868 # TODO: get profile URLs other than user.ap_id
1869 profile_urls = [user.ap_id]
1870
1871 bio
1872 |> CommonUtils.format_input("text/plain",
1873 mentions_format: :full,
1874 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1875 )
1876 |> elem(0)
1877 end
1878
1879 def parse_bio(_, _), do: ""
1880
1881 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1882 Repo.transaction(fn ->
1883 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1884 end)
1885 end
1886
1887 def tag(nickname, tags) when is_binary(nickname),
1888 do: tag(get_by_nickname(nickname), tags)
1889
1890 def tag(%User{} = user, tags),
1891 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1892
1893 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1894 Repo.transaction(fn ->
1895 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1896 end)
1897 end
1898
1899 def untag(nickname, tags) when is_binary(nickname),
1900 do: untag(get_by_nickname(nickname), tags)
1901
1902 def untag(%User{} = user, tags),
1903 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1904
1905 defp update_tags(%User{} = user, new_tags) do
1906 {:ok, updated_user} =
1907 user
1908 |> change(%{tags: new_tags})
1909 |> update_and_set_cache()
1910
1911 updated_user
1912 end
1913
1914 defp normalize_tags(tags) do
1915 [tags]
1916 |> List.flatten()
1917 |> Enum.map(&String.downcase/1)
1918 end
1919
1920 defp local_nickname_regex do
1921 if Config.get([:instance, :extended_nickname_format]) do
1922 @extended_local_nickname_regex
1923 else
1924 @strict_local_nickname_regex
1925 end
1926 end
1927
1928 def local_nickname(nickname_or_mention) do
1929 nickname_or_mention
1930 |> full_nickname()
1931 |> String.split("@")
1932 |> hd()
1933 end
1934
1935 def full_nickname(nickname_or_mention),
1936 do: String.trim_leading(nickname_or_mention, "@")
1937
1938 def error_user(ap_id) do
1939 %User{
1940 name: ap_id,
1941 ap_id: ap_id,
1942 nickname: "erroruser@example.com",
1943 inserted_at: NaiveDateTime.utc_now()
1944 }
1945 end
1946
1947 @spec all_superusers() :: [User.t()]
1948 def all_superusers do
1949 User.Query.build(%{super_users: true, local: true, deactivated: false})
1950 |> Repo.all()
1951 end
1952
1953 def muting_reblogs?(%User{} = user, %User{} = target) do
1954 UserRelationship.reblog_mute_exists?(user, target)
1955 end
1956
1957 def showing_reblogs?(%User{} = user, %User{} = target) do
1958 not muting_reblogs?(user, target)
1959 end
1960
1961 @doc """
1962 The function returns a query to get users with no activity for given interval of days.
1963 Inactive users are those who didn't read any notification, or had any activity where
1964 the user is the activity's actor, during `inactivity_threshold` days.
1965 Deactivated users will not appear in this list.
1966
1967 ## Examples
1968
1969 iex> Pleroma.User.list_inactive_users()
1970 %Ecto.Query{}
1971 """
1972 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1973 def list_inactive_users_query(inactivity_threshold \\ 7) do
1974 negative_inactivity_threshold = -inactivity_threshold
1975 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1976 # Subqueries are not supported in `where` clauses, join gets too complicated.
1977 has_read_notifications =
1978 from(n in Pleroma.Notification,
1979 where: n.seen == true,
1980 group_by: n.id,
1981 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1982 select: n.user_id
1983 )
1984 |> Pleroma.Repo.all()
1985
1986 from(u in Pleroma.User,
1987 left_join: a in Pleroma.Activity,
1988 on: u.ap_id == a.actor,
1989 where: not is_nil(u.nickname),
1990 where: u.deactivated != ^true,
1991 where: u.id not in ^has_read_notifications,
1992 group_by: u.id,
1993 having:
1994 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1995 is_nil(max(a.inserted_at))
1996 )
1997 end
1998
1999 @doc """
2000 Enable or disable email notifications for user
2001
2002 ## Examples
2003
2004 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
2005 Pleroma.User{email_notifications: %{"digest" => true}}
2006
2007 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
2008 Pleroma.User{email_notifications: %{"digest" => false}}
2009 """
2010 @spec switch_email_notifications(t(), String.t(), boolean()) ::
2011 {:ok, t()} | {:error, Ecto.Changeset.t()}
2012 def switch_email_notifications(user, type, status) do
2013 User.update_email_notifications(user, %{type => status})
2014 end
2015
2016 @doc """
2017 Set `last_digest_emailed_at` value for the user to current time
2018 """
2019 @spec touch_last_digest_emailed_at(t()) :: t()
2020 def touch_last_digest_emailed_at(user) do
2021 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2022
2023 {:ok, updated_user} =
2024 user
2025 |> change(%{last_digest_emailed_at: now})
2026 |> update_and_set_cache()
2027
2028 updated_user
2029 end
2030
2031 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
2032 def toggle_confirmation(%User{} = user) do
2033 user
2034 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
2035 |> update_and_set_cache()
2036 end
2037
2038 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
2039 def toggle_confirmation(users) do
2040 Enum.map(users, &toggle_confirmation/1)
2041 end
2042
2043 @spec need_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
2044 def need_confirmation(%User{} = user, bool) do
2045 user
2046 |> confirmation_changeset(need_confirmation: bool)
2047 |> update_and_set_cache()
2048 end
2049
2050 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
2051 mascot
2052 end
2053
2054 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
2055 # use instance-default
2056 config = Config.get([:assets, :mascots])
2057 default_mascot = Config.get([:assets, :default_mascot])
2058 mascot = Keyword.get(config, default_mascot)
2059
2060 %{
2061 "id" => "default-mascot",
2062 "url" => mascot[:url],
2063 "preview_url" => mascot[:url],
2064 "pleroma" => %{
2065 "mime_type" => mascot[:mime_type]
2066 }
2067 }
2068 end
2069
2070 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
2071
2072 def ensure_keys_present(%User{} = user) do
2073 with {:ok, pem} <- Keys.generate_rsa_pem() do
2074 user
2075 |> cast(%{keys: pem}, [:keys])
2076 |> validate_required([:keys])
2077 |> update_and_set_cache()
2078 end
2079 end
2080
2081 def get_ap_ids_by_nicknames(nicknames) do
2082 from(u in User,
2083 where: u.nickname in ^nicknames,
2084 select: u.ap_id
2085 )
2086 |> Repo.all()
2087 end
2088
2089 defp put_password_hash(
2090 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
2091 ) do
2092 change(changeset, password_hash: Pbkdf2.hash_pwd_salt(password))
2093 end
2094
2095 defp put_password_hash(changeset), do: changeset
2096
2097 def is_internal_user?(%User{nickname: nil}), do: true
2098 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
2099 def is_internal_user?(_), do: false
2100
2101 # A hack because user delete activities have a fake id for whatever reason
2102 # TODO: Get rid of this
2103 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
2104
2105 def get_delivered_users_by_object_id(object_id) do
2106 from(u in User,
2107 inner_join: delivery in assoc(u, :deliveries),
2108 where: delivery.object_id == ^object_id
2109 )
2110 |> Repo.all()
2111 end
2112
2113 def change_email(user, email) do
2114 user
2115 |> cast(%{email: email}, [:email])
2116 |> validate_required([:email])
2117 |> unique_constraint(:email)
2118 |> validate_format(:email, @email_regex)
2119 |> update_and_set_cache()
2120 end
2121
2122 # Internal function; public one is `deactivate/2`
2123 defp set_activation_status(user, deactivated) do
2124 user
2125 |> cast(%{deactivated: deactivated}, [:deactivated])
2126 |> update_and_set_cache()
2127 end
2128
2129 def update_banner(user, banner) do
2130 user
2131 |> cast(%{banner: banner}, [:banner])
2132 |> update_and_set_cache()
2133 end
2134
2135 def update_background(user, background) do
2136 user
2137 |> cast(%{background: background}, [:background])
2138 |> update_and_set_cache()
2139 end
2140
2141 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
2142 %{
2143 admin: is_admin,
2144 moderator: is_moderator
2145 }
2146 end
2147
2148 def validate_fields(changeset, remote? \\ false) do
2149 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2150 limit = Config.get([:instance, limit_name], 0)
2151
2152 changeset
2153 |> validate_length(:fields, max: limit)
2154 |> validate_change(:fields, fn :fields, fields ->
2155 if Enum.all?(fields, &valid_field?/1) do
2156 []
2157 else
2158 [fields: "invalid"]
2159 end
2160 end)
2161 end
2162
2163 defp valid_field?(%{"name" => name, "value" => value}) do
2164 name_limit = Config.get([:instance, :account_field_name_length], 255)
2165 value_limit = Config.get([:instance, :account_field_value_length], 255)
2166
2167 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2168 String.length(value) <= value_limit
2169 end
2170
2171 defp valid_field?(_), do: false
2172
2173 defp truncate_field(%{"name" => name, "value" => value}) do
2174 {name, _chopped} =
2175 String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
2176
2177 {value, _chopped} =
2178 String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
2179
2180 %{"name" => name, "value" => value}
2181 end
2182
2183 def admin_api_update(user, params) do
2184 user
2185 |> cast(params, [
2186 :is_moderator,
2187 :is_admin,
2188 :show_role
2189 ])
2190 |> update_and_set_cache()
2191 end
2192
2193 @doc "Signs user out of all applications"
2194 def global_sign_out(user) do
2195 OAuth.Authorization.delete_user_authorizations(user)
2196 OAuth.Token.delete_user_tokens(user)
2197 end
2198
2199 def mascot_update(user, url) do
2200 user
2201 |> cast(%{mascot: url}, [:mascot])
2202 |> validate_required([:mascot])
2203 |> update_and_set_cache()
2204 end
2205
2206 def mastodon_settings_update(user, settings) do
2207 user
2208 |> cast(%{mastofe_settings: settings}, [:mastofe_settings])
2209 |> validate_required([:mastofe_settings])
2210 |> update_and_set_cache()
2211 end
2212
2213 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2214 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
2215 params =
2216 if need_confirmation? do
2217 %{
2218 confirmation_pending: true,
2219 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2220 }
2221 else
2222 %{
2223 confirmation_pending: false,
2224 confirmation_token: nil
2225 }
2226 end
2227
2228 cast(user, params, [:confirmation_pending, :confirmation_token])
2229 end
2230
2231 @spec approval_changeset(User.t(), keyword()) :: Changeset.t()
2232 def approval_changeset(user, need_approval: need_approval?) do
2233 params = if need_approval?, do: %{approval_pending: true}, else: %{approval_pending: false}
2234 cast(user, params, [:approval_pending])
2235 end
2236
2237 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2238 if id not in user.pinned_activities do
2239 max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
2240 params = %{pinned_activities: user.pinned_activities ++ [id]}
2241
2242 # if pinned activity was scheduled for deletion, we remove job
2243 if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(id) do
2244 Oban.cancel_job(expiration.id)
2245 end
2246
2247 user
2248 |> cast(params, [:pinned_activities])
2249 |> validate_length(:pinned_activities,
2250 max: max_pinned_statuses,
2251 message: "You have already pinned the maximum number of statuses"
2252 )
2253 else
2254 change(user)
2255 end
2256 |> update_and_set_cache()
2257 end
2258
2259 def remove_pinnned_activity(user, %Pleroma.Activity{id: id, data: data}) do
2260 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2261
2262 # if pinned activity was scheduled for deletion, we reschedule it for deletion
2263 if data["expires_at"] do
2264 # MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation
2265 {:ok, expires_at} =
2266 data["expires_at"] |> Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast()
2267
2268 Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
2269 activity_id: id,
2270 expires_at: expires_at
2271 })
2272 end
2273
2274 user
2275 |> cast(params, [:pinned_activities])
2276 |> update_and_set_cache()
2277 end
2278
2279 def update_email_notifications(user, settings) do
2280 email_notifications =
2281 user.email_notifications
2282 |> Map.merge(settings)
2283 |> Map.take(["digest"])
2284
2285 params = %{email_notifications: email_notifications}
2286 fields = [:email_notifications]
2287
2288 user
2289 |> cast(params, fields)
2290 |> validate_required(fields)
2291 |> update_and_set_cache()
2292 end
2293
2294 defp set_domain_blocks(user, domain_blocks) do
2295 params = %{domain_blocks: domain_blocks}
2296
2297 user
2298 |> cast(params, [:domain_blocks])
2299 |> validate_required([:domain_blocks])
2300 |> update_and_set_cache()
2301 end
2302
2303 def block_domain(user, domain_blocked) do
2304 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2305 end
2306
2307 def unblock_domain(user, domain_blocked) do
2308 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2309 end
2310
2311 @spec add_to_block(User.t(), User.t()) ::
2312 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2313 defp add_to_block(%User{} = user, %User{} = blocked) do
2314 UserRelationship.create_block(user, blocked)
2315 end
2316
2317 @spec add_to_block(User.t(), User.t()) ::
2318 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2319 defp remove_from_block(%User{} = user, %User{} = blocked) do
2320 UserRelationship.delete_block(user, blocked)
2321 end
2322
2323 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2324 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2325 {:ok, user_notification_mute} <-
2326 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2327 {:ok, nil} do
2328 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2329 end
2330 end
2331
2332 defp remove_from_mutes(user, %User{} = muted_user) do
2333 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2334 {:ok, user_notification_mute} <-
2335 UserRelationship.delete_notification_mute(user, muted_user) do
2336 {:ok, [user_mute, user_notification_mute]}
2337 end
2338 end
2339
2340 def set_invisible(user, invisible) do
2341 params = %{invisible: invisible}
2342
2343 user
2344 |> cast(params, [:invisible])
2345 |> validate_required([:invisible])
2346 |> update_and_set_cache()
2347 end
2348
2349 def sanitize_html(%User{} = user) do
2350 sanitize_html(user, nil)
2351 end
2352
2353 # User data that mastodon isn't filtering (treated as plaintext):
2354 # - field name
2355 # - display name
2356 def sanitize_html(%User{} = user, filter) do
2357 fields =
2358 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2359 %{
2360 "name" => name,
2361 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2362 }
2363 end)
2364
2365 user
2366 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2367 |> Map.put(:fields, fields)
2368 end
2369 end