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