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