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