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