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