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