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