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