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