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