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