5d8b936aa3ac599d44c90385316e1118f0dbf76d
[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 password_reset_pending: false,
1696 registration_reason: nil,
1697 confirmation_token: nil,
1698 domain_blocks: [],
1699 is_active: false,
1700 ap_enabled: false,
1701 is_moderator: false,
1702 is_admin: false,
1703 mastofe_settings: nil,
1704 mascot: nil,
1705 emoji: %{},
1706 pleroma_settings_store: %{},
1707 fields: [],
1708 raw_fields: [],
1709 is_discoverable: false,
1710 also_known_as: []
1711 # id: preserved
1712 # ap_id: preserved
1713 # nickname: preserved
1714 })
1715 end
1716
1717 # Purge doesn't delete the user from the database.
1718 # It just nulls all its fields and deactivates it.
1719 # See `User.purge_user_changeset/1` above.
1720 def purge(%User{} = user) do
1721 user
1722 |> purge_user_changeset()
1723 |> update_and_set_cache()
1724 end
1725
1726 def delete(users) when is_list(users) do
1727 for user <- users, do: delete(user)
1728 end
1729
1730 def delete(%User{} = user) do
1731 # Purge the user immediately
1732 purge(user)
1733 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1734 end
1735
1736 # *Actually* delete the user from the DB
1737 defp delete_from_db(%User{} = user) do
1738 invalidate_cache(user)
1739 Repo.delete(user)
1740 end
1741
1742 # If the user never finalized their account, it's safe to delete them.
1743 defp maybe_delete_from_db(%User{local: true, is_confirmed: false} = user),
1744 do: delete_from_db(user)
1745
1746 defp maybe_delete_from_db(%User{local: true, is_approved: false} = user),
1747 do: delete_from_db(user)
1748
1749 defp maybe_delete_from_db(user), do: {:ok, user}
1750
1751 def perform(:force_password_reset, user), do: force_password_reset(user)
1752
1753 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1754 def perform(:delete, %User{} = user) do
1755 # Purge the user again, in case perform/2 is called directly
1756 purge(user)
1757
1758 # Remove all relationships
1759 user
1760 |> get_followers()
1761 |> Enum.each(fn follower ->
1762 ActivityPub.unfollow(follower, user)
1763 unfollow(follower, user)
1764 end)
1765
1766 user
1767 |> get_friends()
1768 |> Enum.each(fn followed ->
1769 ActivityPub.unfollow(user, followed)
1770 unfollow(user, followed)
1771 end)
1772
1773 delete_user_activities(user)
1774 delete_notifications_from_user_activities(user)
1775 delete_outgoing_pending_follow_requests(user)
1776
1777 maybe_delete_from_db(user)
1778 end
1779
1780 def perform(:set_activation_async, user, status), do: set_activation(user, status)
1781
1782 @spec external_users_query() :: Ecto.Query.t()
1783 def external_users_query do
1784 User.Query.build(%{
1785 external: true,
1786 active: true,
1787 order_by: :id
1788 })
1789 end
1790
1791 @spec external_users(keyword()) :: [User.t()]
1792 def external_users(opts \\ []) do
1793 query =
1794 external_users_query()
1795 |> select([u], struct(u, [:id, :ap_id]))
1796
1797 query =
1798 if opts[:max_id],
1799 do: where(query, [u], u.id > ^opts[:max_id]),
1800 else: query
1801
1802 query =
1803 if opts[:limit],
1804 do: limit(query, ^opts[:limit]),
1805 else: query
1806
1807 Repo.all(query)
1808 end
1809
1810 def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
1811 Notification
1812 |> join(:inner, [n], activity in assoc(n, :activity))
1813 |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
1814 |> Repo.delete_all()
1815 end
1816
1817 def delete_user_activities(%User{ap_id: ap_id} = user) do
1818 ap_id
1819 |> Activity.Queries.by_actor()
1820 |> Repo.chunk_stream(50, :batches)
1821 |> Stream.each(fn activities ->
1822 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
1823 end)
1824 |> Stream.run()
1825 end
1826
1827 defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
1828 with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
1829 {:ok, delete_data, _} <- Builder.delete(user, object) do
1830 Pipeline.common_pipeline(delete_data, local: user.local)
1831 else
1832 {:find_object, nil} ->
1833 # We have the create activity, but not the object, it was probably pruned.
1834 # Insert a tombstone and try again
1835 with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
1836 {:ok, _tombstone} <- Object.create(tombstone_data) do
1837 delete_activity(activity, user)
1838 end
1839
1840 e ->
1841 Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
1842 Logger.error("Error: #{inspect(e)}")
1843 end
1844 end
1845
1846 defp delete_activity(%{data: %{"type" => type}} = activity, user)
1847 when type in ["Like", "Announce"] do
1848 {:ok, undo, _} = Builder.undo(user, activity)
1849 Pipeline.common_pipeline(undo, local: user.local)
1850 end
1851
1852 defp delete_activity(_activity, _user), do: "Doing nothing"
1853
1854 defp delete_outgoing_pending_follow_requests(user) do
1855 user
1856 |> FollowingRelationship.outgoing_pending_follow_requests_query()
1857 |> Repo.delete_all()
1858 end
1859
1860 def html_filter_policy(%User{no_rich_text: true}) do
1861 Pleroma.HTML.Scrubber.TwitterText
1862 end
1863
1864 def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
1865
1866 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1867
1868 def get_or_fetch_by_ap_id(ap_id) do
1869 cached_user = get_cached_by_ap_id(ap_id)
1870
1871 maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
1872
1873 case {cached_user, maybe_fetched_user} do
1874 {_, {:ok, %User{} = user}} ->
1875 {:ok, user}
1876
1877 {%User{} = user, _} ->
1878 {:ok, user}
1879
1880 _ ->
1881 {:error, :not_found}
1882 end
1883 end
1884
1885 @doc """
1886 Creates an internal service actor by URI if missing.
1887 Optionally takes nickname for addressing.
1888 """
1889 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1890 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1891 {_, user} =
1892 case get_cached_by_ap_id(uri) do
1893 nil ->
1894 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1895 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1896 {:error, nil}
1897 end
1898
1899 %User{invisible: false} = user ->
1900 set_invisible(user)
1901
1902 user ->
1903 {:ok, user}
1904 end
1905
1906 user
1907 end
1908
1909 @spec set_invisible(User.t()) :: {:ok, User.t()}
1910 defp set_invisible(user) do
1911 user
1912 |> change(%{invisible: true})
1913 |> update_and_set_cache()
1914 end
1915
1916 @spec create_service_actor(String.t(), String.t()) ::
1917 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1918 defp create_service_actor(uri, nickname) do
1919 %User{
1920 invisible: true,
1921 local: true,
1922 ap_id: uri,
1923 nickname: nickname,
1924 follower_address: uri <> "/followers"
1925 }
1926 |> change
1927 |> unique_constraint(:nickname)
1928 |> Repo.insert()
1929 |> set_cache()
1930 end
1931
1932 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1933 key =
1934 public_key_pem
1935 |> :public_key.pem_decode()
1936 |> hd()
1937 |> :public_key.pem_entry_decode()
1938
1939 {:ok, key}
1940 end
1941
1942 def public_key(_), do: {:error, "key not found"}
1943
1944 def get_public_key_for_ap_id(ap_id) do
1945 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1946 {:ok, public_key} <- public_key(user) do
1947 {:ok, public_key}
1948 else
1949 _ -> :error
1950 end
1951 end
1952
1953 def ap_enabled?(%User{local: true}), do: true
1954 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1955 def ap_enabled?(_), do: false
1956
1957 @doc "Gets or fetch a user by uri or nickname."
1958 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1959 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1960 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1961
1962 # wait a period of time and return newest version of the User structs
1963 # this is because we have synchronous follow APIs and need to simulate them
1964 # with an async handshake
1965 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1966 with %User{} = a <- get_cached_by_id(a.id),
1967 %User{} = b <- get_cached_by_id(b.id) do
1968 {:ok, a, b}
1969 else
1970 nil -> :error
1971 end
1972 end
1973
1974 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1975 with :ok <- :timer.sleep(timeout),
1976 %User{} = a <- get_cached_by_id(a.id),
1977 %User{} = b <- get_cached_by_id(b.id) do
1978 {:ok, a, b}
1979 else
1980 nil -> :error
1981 end
1982 end
1983
1984 def parse_bio(bio) when is_binary(bio) and bio != "" do
1985 bio
1986 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1987 |> elem(0)
1988 end
1989
1990 def parse_bio(_), do: ""
1991
1992 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1993 # TODO: get profile URLs other than user.ap_id
1994 profile_urls = [user.ap_id]
1995
1996 bio
1997 |> CommonUtils.format_input("text/plain",
1998 mentions_format: :full,
1999 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
2000 )
2001 |> elem(0)
2002 end
2003
2004 def parse_bio(_, _), do: ""
2005
2006 def tag(user_identifiers, tags) when is_list(user_identifiers) do
2007 Repo.transaction(fn ->
2008 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
2009 end)
2010 end
2011
2012 def tag(nickname, tags) when is_binary(nickname),
2013 do: tag(get_by_nickname(nickname), tags)
2014
2015 def tag(%User{} = user, tags),
2016 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
2017
2018 def untag(user_identifiers, tags) when is_list(user_identifiers) do
2019 Repo.transaction(fn ->
2020 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
2021 end)
2022 end
2023
2024 def untag(nickname, tags) when is_binary(nickname),
2025 do: untag(get_by_nickname(nickname), tags)
2026
2027 def untag(%User{} = user, tags),
2028 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
2029
2030 defp update_tags(%User{} = user, new_tags) do
2031 {:ok, updated_user} =
2032 user
2033 |> change(%{tags: new_tags})
2034 |> update_and_set_cache()
2035
2036 updated_user
2037 end
2038
2039 defp normalize_tags(tags) do
2040 [tags]
2041 |> List.flatten()
2042 |> Enum.map(&String.downcase/1)
2043 end
2044
2045 defp local_nickname_regex do
2046 if Config.get([:instance, :extended_nickname_format]) do
2047 @extended_local_nickname_regex
2048 else
2049 @strict_local_nickname_regex
2050 end
2051 end
2052
2053 def local_nickname(nickname_or_mention) do
2054 nickname_or_mention
2055 |> full_nickname()
2056 |> String.split("@")
2057 |> hd()
2058 end
2059
2060 def full_nickname(%User{} = user) do
2061 if String.contains?(user.nickname, "@") do
2062 user.nickname
2063 else
2064 %{host: host} = URI.parse(user.ap_id)
2065 user.nickname <> "@" <> host
2066 end
2067 end
2068
2069 def full_nickname(nickname_or_mention),
2070 do: String.trim_leading(nickname_or_mention, "@")
2071
2072 def error_user(ap_id) do
2073 %User{
2074 name: ap_id,
2075 ap_id: ap_id,
2076 nickname: "erroruser@example.com",
2077 inserted_at: NaiveDateTime.utc_now()
2078 }
2079 end
2080
2081 @spec all_superusers() :: [User.t()]
2082 def all_superusers do
2083 User.Query.build(%{super_users: true, local: true, is_active: true})
2084 |> Repo.all()
2085 end
2086
2087 def muting_reblogs?(%User{} = user, %User{} = target) do
2088 UserRelationship.reblog_mute_exists?(user, target)
2089 end
2090
2091 def showing_reblogs?(%User{} = user, %User{} = target) do
2092 not muting_reblogs?(user, target)
2093 end
2094
2095 @doc """
2096 The function returns a query to get users with no activity for given interval of days.
2097 Inactive users are those who didn't read any notification, or had any activity where
2098 the user is the activity's actor, during `inactivity_threshold` days.
2099 Deactivated users will not appear in this list.
2100
2101 ## Examples
2102
2103 iex> Pleroma.User.list_inactive_users()
2104 %Ecto.Query{}
2105 """
2106 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
2107 def list_inactive_users_query(inactivity_threshold \\ 7) do
2108 negative_inactivity_threshold = -inactivity_threshold
2109 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2110 # Subqueries are not supported in `where` clauses, join gets too complicated.
2111 has_read_notifications =
2112 from(n in Pleroma.Notification,
2113 where: n.seen == true,
2114 group_by: n.id,
2115 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
2116 select: n.user_id
2117 )
2118 |> Pleroma.Repo.all()
2119
2120 from(u in Pleroma.User,
2121 left_join: a in Pleroma.Activity,
2122 on: u.ap_id == a.actor,
2123 where: not is_nil(u.nickname),
2124 where: u.is_active == ^true,
2125 where: u.id not in ^has_read_notifications,
2126 group_by: u.id,
2127 having:
2128 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
2129 is_nil(max(a.inserted_at))
2130 )
2131 end
2132
2133 @doc """
2134 Enable or disable email notifications for user
2135
2136 ## Examples
2137
2138 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
2139 Pleroma.User{email_notifications: %{"digest" => true}}
2140
2141 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
2142 Pleroma.User{email_notifications: %{"digest" => false}}
2143 """
2144 @spec switch_email_notifications(t(), String.t(), boolean()) ::
2145 {:ok, t()} | {:error, Ecto.Changeset.t()}
2146 def switch_email_notifications(user, type, status) do
2147 User.update_email_notifications(user, %{type => status})
2148 end
2149
2150 @doc """
2151 Set `last_digest_emailed_at` value for the user to current time
2152 """
2153 @spec touch_last_digest_emailed_at(t()) :: t()
2154 def touch_last_digest_emailed_at(user) do
2155 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2156
2157 {:ok, updated_user} =
2158 user
2159 |> change(%{last_digest_emailed_at: now})
2160 |> update_and_set_cache()
2161
2162 updated_user
2163 end
2164
2165 @spec set_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
2166 def set_confirmation(%User{} = user, bool) do
2167 user
2168 |> confirmation_changeset(set_confirmation: bool)
2169 |> update_and_set_cache()
2170 end
2171
2172 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
2173 mascot
2174 end
2175
2176 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
2177 # use instance-default
2178 config = Config.get([:assets, :mascots])
2179 default_mascot = Config.get([:assets, :default_mascot])
2180 mascot = Keyword.get(config, default_mascot)
2181
2182 %{
2183 "id" => "default-mascot",
2184 "url" => mascot[:url],
2185 "preview_url" => mascot[:url],
2186 "pleroma" => %{
2187 "mime_type" => mascot[:mime_type]
2188 }
2189 }
2190 end
2191
2192 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
2193
2194 def ensure_keys_present(%User{} = user) do
2195 with {:ok, pem} <- Keys.generate_rsa_pem() do
2196 user
2197 |> cast(%{keys: pem}, [:keys])
2198 |> validate_required([:keys])
2199 |> update_and_set_cache()
2200 end
2201 end
2202
2203 def get_ap_ids_by_nicknames(nicknames) do
2204 from(u in User,
2205 where: u.nickname in ^nicknames,
2206 select: u.ap_id
2207 )
2208 |> Repo.all()
2209 end
2210
2211 defp put_password_hash(
2212 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
2213 ) do
2214 change(changeset, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
2215 end
2216
2217 defp put_password_hash(changeset), do: changeset
2218
2219 def is_internal_user?(%User{nickname: nil}), do: true
2220 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
2221 def is_internal_user?(_), do: false
2222
2223 # A hack because user delete activities have a fake id for whatever reason
2224 # TODO: Get rid of this
2225 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
2226
2227 def get_delivered_users_by_object_id(object_id) do
2228 from(u in User,
2229 inner_join: delivery in assoc(u, :deliveries),
2230 where: delivery.object_id == ^object_id
2231 )
2232 |> Repo.all()
2233 end
2234
2235 def change_email(user, email) do
2236 user
2237 |> cast(%{email: email}, [:email])
2238 |> validate_required([:email])
2239 |> unique_constraint(:email)
2240 |> validate_format(:email, @email_regex)
2241 |> update_and_set_cache()
2242 end
2243
2244 # Internal function; public one is `deactivate/2`
2245 defp set_activation_status(user, status) do
2246 user
2247 |> cast(%{is_active: status}, [:is_active])
2248 |> update_and_set_cache()
2249 end
2250
2251 def update_banner(user, banner) do
2252 user
2253 |> cast(%{banner: banner}, [:banner])
2254 |> update_and_set_cache()
2255 end
2256
2257 def update_background(user, background) do
2258 user
2259 |> cast(%{background: background}, [:background])
2260 |> update_and_set_cache()
2261 end
2262
2263 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
2264 %{
2265 admin: is_admin,
2266 moderator: is_moderator
2267 }
2268 end
2269
2270 def validate_fields(changeset, remote? \\ false) do
2271 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2272 limit = Config.get([:instance, limit_name], 0)
2273
2274 changeset
2275 |> validate_length(:fields, max: limit)
2276 |> validate_change(:fields, fn :fields, fields ->
2277 if Enum.all?(fields, &valid_field?/1) do
2278 []
2279 else
2280 [fields: "invalid"]
2281 end
2282 end)
2283 end
2284
2285 defp valid_field?(%{"name" => name, "value" => value}) do
2286 name_limit = Config.get([:instance, :account_field_name_length], 255)
2287 value_limit = Config.get([:instance, :account_field_value_length], 255)
2288
2289 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2290 String.length(value) <= value_limit
2291 end
2292
2293 defp valid_field?(_), do: false
2294
2295 defp truncate_field(%{"name" => name, "value" => value}) do
2296 {name, _chopped} =
2297 String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
2298
2299 {value, _chopped} =
2300 String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
2301
2302 %{"name" => name, "value" => value}
2303 end
2304
2305 def admin_api_update(user, params) do
2306 user
2307 |> cast(params, [
2308 :is_moderator,
2309 :is_admin,
2310 :show_role
2311 ])
2312 |> update_and_set_cache()
2313 end
2314
2315 @doc "Signs user out of all applications"
2316 def global_sign_out(user) do
2317 OAuth.Authorization.delete_user_authorizations(user)
2318 OAuth.Token.delete_user_tokens(user)
2319 end
2320
2321 def mascot_update(user, url) do
2322 user
2323 |> cast(%{mascot: url}, [:mascot])
2324 |> validate_required([:mascot])
2325 |> update_and_set_cache()
2326 end
2327
2328 def mastodon_settings_update(user, settings) do
2329 user
2330 |> cast(%{mastofe_settings: settings}, [:mastofe_settings])
2331 |> validate_required([:mastofe_settings])
2332 |> update_and_set_cache()
2333 end
2334
2335 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2336 def confirmation_changeset(user, set_confirmation: confirmed?) do
2337 params =
2338 if confirmed? do
2339 %{
2340 is_confirmed: true,
2341 confirmation_token: nil
2342 }
2343 else
2344 %{
2345 is_confirmed: false,
2346 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2347 }
2348 end
2349
2350 cast(user, params, [:is_confirmed, :confirmation_token])
2351 end
2352
2353 @spec approval_changeset(User.t(), keyword()) :: Changeset.t()
2354 def approval_changeset(user, set_approval: approved?) do
2355 cast(user, %{is_approved: approved?}, [:is_approved])
2356 end
2357
2358 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2359 if id not in user.pinned_activities do
2360 max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
2361 params = %{pinned_activities: user.pinned_activities ++ [id]}
2362
2363 # if pinned activity was scheduled for deletion, we remove job
2364 if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(id) do
2365 Oban.cancel_job(expiration.id)
2366 end
2367
2368 user
2369 |> cast(params, [:pinned_activities])
2370 |> validate_length(:pinned_activities,
2371 max: max_pinned_statuses,
2372 message: "You have already pinned the maximum number of statuses"
2373 )
2374 else
2375 change(user)
2376 end
2377 |> update_and_set_cache()
2378 end
2379
2380 def remove_pinnned_activity(user, %Pleroma.Activity{id: id, data: data}) do
2381 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2382
2383 # if pinned activity was scheduled for deletion, we reschedule it for deletion
2384 if data["expires_at"] do
2385 # MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation
2386 {:ok, expires_at} =
2387 data["expires_at"] |> Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast()
2388
2389 Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
2390 activity_id: id,
2391 expires_at: expires_at
2392 })
2393 end
2394
2395 user
2396 |> cast(params, [:pinned_activities])
2397 |> update_and_set_cache()
2398 end
2399
2400 def update_email_notifications(user, settings) do
2401 email_notifications =
2402 user.email_notifications
2403 |> Map.merge(settings)
2404 |> Map.take(["digest"])
2405
2406 params = %{email_notifications: email_notifications}
2407 fields = [:email_notifications]
2408
2409 user
2410 |> cast(params, fields)
2411 |> validate_required(fields)
2412 |> update_and_set_cache()
2413 end
2414
2415 defp set_domain_blocks(user, domain_blocks) do
2416 params = %{domain_blocks: domain_blocks}
2417
2418 user
2419 |> cast(params, [:domain_blocks])
2420 |> validate_required([:domain_blocks])
2421 |> update_and_set_cache()
2422 end
2423
2424 def block_domain(user, domain_blocked) do
2425 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2426 end
2427
2428 def unblock_domain(user, domain_blocked) do
2429 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2430 end
2431
2432 @spec add_to_block(User.t(), User.t()) ::
2433 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2434 defp add_to_block(%User{} = user, %User{} = blocked) do
2435 with {:ok, relationship} <- UserRelationship.create_block(user, blocked) do
2436 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2437 {:ok, relationship}
2438 end
2439 end
2440
2441 @spec add_to_block(User.t(), User.t()) ::
2442 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2443 defp remove_from_block(%User{} = user, %User{} = blocked) do
2444 with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do
2445 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2446 {:ok, relationship}
2447 end
2448 end
2449
2450 def set_invisible(user, invisible) do
2451 params = %{invisible: invisible}
2452
2453 user
2454 |> cast(params, [:invisible])
2455 |> validate_required([:invisible])
2456 |> update_and_set_cache()
2457 end
2458
2459 def sanitize_html(%User{} = user) do
2460 sanitize_html(user, nil)
2461 end
2462
2463 # User data that mastodon isn't filtering (treated as plaintext):
2464 # - field name
2465 # - display name
2466 def sanitize_html(%User{} = user, filter) do
2467 fields =
2468 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2469 %{
2470 "name" => name,
2471 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2472 }
2473 end)
2474
2475 user
2476 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2477 |> Map.put(:fields, fields)
2478 end
2479
2480 def get_host(%User{ap_id: ap_id} = _user) do
2481 URI.parse(ap_id).host
2482 end
2483
2484 def update_last_active_at(%__MODULE__{local: true} = user) do
2485 user
2486 |> cast(%{last_active_at: NaiveDateTime.utc_now()}, [:last_active_at])
2487 |> update_and_set_cache()
2488 end
2489
2490 def active_user_count(weeks \\ 4) do
2491 active_after = Timex.shift(NaiveDateTime.utc_now(), weeks: -weeks)
2492
2493 __MODULE__
2494 |> where([u], u.last_active_at >= ^active_after)
2495 |> where([u], u.local == true)
2496 |> Repo.aggregate(:count)
2497 end
2498 end