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