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