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