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