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