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