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