remove all endpoints marked as deprecated (#91)
[akkoma] / lib / pleroma / user.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.User do
6 use Ecto.Schema
7
8 import Ecto.Changeset
9 import Ecto.Query
10 import Ecto, only: [assoc: 2]
11
12 alias Ecto.Multi
13 alias Pleroma.Activity
14 alias Pleroma.Config
15 alias Pleroma.Conversation.Participation
16 alias Pleroma.Delivery
17 alias Pleroma.EctoType.ActivityPub.ObjectValidators
18 alias Pleroma.Emoji
19 alias Pleroma.FollowingRelationship
20 alias Pleroma.Formatter
21 alias Pleroma.HTML
22 alias Pleroma.Keys
23 alias Pleroma.MFA
24 alias Pleroma.Notification
25 alias Pleroma.Object
26 alias Pleroma.Registration
27 alias Pleroma.Repo
28 alias Pleroma.User
29 alias Pleroma.UserRelationship
30 alias Pleroma.Web.ActivityPub.ActivityPub
31 alias Pleroma.Web.ActivityPub.Builder
32 alias Pleroma.Web.ActivityPub.Pipeline
33 alias Pleroma.Web.ActivityPub.Utils
34 alias Pleroma.Web.CommonAPI
35 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
36 alias Pleroma.Web.Endpoint
37 alias Pleroma.Web.OAuth
38 alias Pleroma.Web.RelMe
39 alias Pleroma.Workers.BackgroundWorker
40
41 require Logger
42
43 @type t :: %__MODULE__{}
44 @type account_status ::
45 :active
46 | :deactivated
47 | :password_reset_pending
48 | :confirmation_pending
49 | :approval_pending
50 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
51
52 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
53 @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
54
55 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
56 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
57
58 # AP ID user relationships (blocks, mutes etc.)
59 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
60 @user_relationships_config [
61 block: [
62 blocker_blocks: :blocked_users,
63 blockee_blocks: :blocker_users
64 ],
65 mute: [
66 muter_mutes: :muted_users,
67 mutee_mutes: :muter_users
68 ],
69 reblog_mute: [
70 reblog_muter_mutes: :reblog_muted_users,
71 reblog_mutee_mutes: :reblog_muter_users
72 ],
73 notification_mute: [
74 notification_muter_mutes: :notification_muted_users,
75 notification_mutee_mutes: :notification_muter_users
76 ],
77 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
78 inverse_subscription: [
79 subscribee_subscriptions: :subscriber_users,
80 subscriber_subscriptions: :subscribee_users
81 ]
82 ]
83
84 @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
85
86 schema "users" do
87 field(:bio, :string, default: "")
88 field(:raw_bio, :string)
89 field(:email, :string)
90 field(:name, :string)
91 field(:nickname, :string)
92 field(:password_hash, :string)
93 field(:password, :string, virtual: true)
94 field(:password_confirmation, :string, virtual: true)
95 field(:keys, :string)
96 field(:public_key, :string)
97 field(:ap_id, :string)
98 field(:avatar, :map, default: %{})
99 field(:local, :boolean, default: true)
100 field(:follower_address, :string)
101 field(:following_address, :string)
102 field(:featured_address, :string)
103 field(:search_rank, :float, virtual: true)
104 field(:search_type, :integer, virtual: true)
105 field(:tags, {:array, :string}, default: [])
106 field(:last_refreshed_at, :naive_datetime_usec)
107 field(:last_digest_emailed_at, :naive_datetime)
108 field(:banner, :map, default: %{})
109 field(:background, :map, default: %{})
110 field(:note_count, :integer, default: 0)
111 field(:follower_count, :integer, default: 0)
112 field(:following_count, :integer, default: 0)
113 field(:is_locked, :boolean, default: false)
114 field(:is_confirmed, :boolean, default: true)
115 field(:password_reset_pending, :boolean, default: false)
116 field(:is_approved, :boolean, default: true)
117 field(:registration_reason, :string, default: nil)
118 field(:confirmation_token, :string, default: nil)
119 field(:default_scope, :string, default: "public")
120 field(:domain_blocks, {:array, :string}, default: [])
121 field(:is_active, :boolean, default: true)
122 field(:no_rich_text, :boolean, default: false)
123 field(:ap_enabled, :boolean, default: false)
124 field(:is_moderator, :boolean, default: false)
125 field(:is_admin, :boolean, default: false)
126 field(:show_role, :boolean, default: true)
127 field(:mastofe_settings, :map, default: nil)
128 field(:uri, ObjectValidators.Uri, default: nil)
129 field(:hide_followers_count, :boolean, default: false)
130 field(:hide_follows_count, :boolean, default: false)
131 field(:hide_followers, :boolean, default: false)
132 field(:hide_follows, :boolean, default: false)
133 field(:hide_favorites, :boolean, default: true)
134 field(:email_notifications, :map, default: %{"digest" => false})
135 field(:mascot, :map, default: nil)
136 field(:emoji, :map, default: %{})
137 field(:pleroma_settings_store, :map, default: %{})
138 field(:fields, {:array, :map}, default: [])
139 field(:raw_fields, {:array, :map}, default: [])
140 field(:is_discoverable, :boolean, default: false)
141 field(:invisible, :boolean, default: false)
142 field(:allow_following_move, :boolean, default: true)
143 field(:skip_thread_containment, :boolean, default: false)
144 field(:actor_type, :string, default: "Person")
145 field(:also_known_as, {:array, ObjectValidators.ObjectID}, default: [])
146 field(:inbox, :string)
147 field(:shared_inbox, :string)
148 field(:accepts_chat_messages, :boolean, default: nil)
149 field(:last_active_at, :naive_datetime)
150 field(:disclose_client, :boolean, default: true)
151 field(:pinned_objects, :map, default: %{})
152 field(:is_suggested, :boolean, default: false)
153 field(:last_status_at, :naive_datetime)
154 field(:language, :string)
155
156 embeds_one(
157 :notification_settings,
158 Pleroma.User.NotificationSetting,
159 on_replace: :update
160 )
161
162 has_many(:notifications, Notification)
163 has_many(:registrations, Registration)
164 has_many(:deliveries, Delivery)
165
166 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
167 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
168
169 for {relationship_type,
170 [
171 {outgoing_relation, outgoing_relation_target},
172 {incoming_relation, incoming_relation_source}
173 ]} <- @user_relationships_config do
174 # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
175 # :notification_muter_mutes, :subscribee_subscriptions
176 has_many(outgoing_relation, UserRelationship,
177 foreign_key: :source_id,
178 where: [relationship_type: relationship_type]
179 )
180
181 # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
182 # :notification_mutee_mutes, :subscriber_subscriptions
183 has_many(incoming_relation, UserRelationship,
184 foreign_key: :target_id,
185 where: [relationship_type: relationship_type]
186 )
187
188 # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
189 # :notification_muted_users, :subscriber_users
190 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
191
192 # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
193 # :notification_muter_users, :subscribee_users
194 has_many(incoming_relation_source, through: [incoming_relation, :source])
195 end
196
197 embeds_one(
198 :multi_factor_authentication_settings,
199 MFA.Settings,
200 on_replace: :delete
201 )
202
203 timestamps()
204 end
205
206 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
207 @user_relationships_config do
208 # `def blocked_users_relation/2`, `def muted_users_relation/2`,
209 # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
210 # `def subscriber_users/2`
211 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
212 target_users_query = assoc(user, unquote(outgoing_relation_target))
213
214 if restrict_deactivated? do
215 target_users_query
216 |> User.Query.build(%{deactivated: false})
217 else
218 target_users_query
219 end
220 end
221
222 # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
223 # `def notification_muted_users/2`, `def subscriber_users/2`
224 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
225 __MODULE__
226 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
227 user,
228 restrict_deactivated?
229 ])
230 |> Repo.all()
231 end
232
233 # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
234 # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
235 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
236 __MODULE__
237 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
238 user,
239 restrict_deactivated?
240 ])
241 |> select([u], u.ap_id)
242 |> Repo.all()
243 end
244 end
245
246 def cached_blocked_users_ap_ids(user) do
247 @cachex.fetch!(:user_cache, "blocked_users_ap_ids:#{user.ap_id}", fn _ ->
248 blocked_users_ap_ids(user)
249 end)
250 end
251
252 def cached_muted_users_ap_ids(user) do
253 @cachex.fetch!(:user_cache, "muted_users_ap_ids:#{user.ap_id}", fn _ ->
254 muted_users_ap_ids(user)
255 end)
256 end
257
258 defdelegate following_count(user), to: FollowingRelationship
259 defdelegate following(user), to: FollowingRelationship
260 defdelegate following?(follower, followed), to: FollowingRelationship
261 defdelegate following_ap_ids(user), to: FollowingRelationship
262 defdelegate get_follow_requests(user), to: FollowingRelationship
263 defdelegate search(query, opts \\ []), to: User.Search
264
265 @doc """
266 Dumps Flake Id to SQL-compatible format (16-byte UUID).
267 E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
268 """
269 def binary_id(source_id) when is_binary(source_id) do
270 with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
271 dumped_id
272 else
273 _ -> source_id
274 end
275 end
276
277 def binary_id(source_ids) when is_list(source_ids) do
278 Enum.map(source_ids, &binary_id/1)
279 end
280
281 def binary_id(%User{} = user), do: binary_id(user.id)
282
283 @doc "Returns status account"
284 @spec account_status(User.t()) :: account_status()
285 def account_status(%User{is_active: false}), do: :deactivated
286 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
287 def account_status(%User{local: true, is_approved: false}), do: :approval_pending
288 def account_status(%User{local: true, is_confirmed: false}), do: :confirmation_pending
289 def account_status(%User{}), do: :active
290
291 @spec visible_for(User.t(), User.t() | nil) ::
292 :visible
293 | :invisible
294 | :restricted_unauthenticated
295 | :deactivated
296 | :confirmation_pending
297 def visible_for(user, for_user \\ nil)
298
299 def visible_for(%User{invisible: true}, _), do: :invisible
300
301 def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
302
303 def visible_for(%User{} = user, nil) do
304 if restrict_unauthenticated?(user) do
305 :restrict_unauthenticated
306 else
307 visible_account_status(user)
308 end
309 end
310
311 def visible_for(%User{} = user, for_user) do
312 if superuser?(for_user) do
313 :visible
314 else
315 visible_account_status(user)
316 end
317 end
318
319 def visible_for(_, _), do: :invisible
320
321 defp restrict_unauthenticated?(%User{local: true}) do
322 Config.restrict_unauthenticated_access?(:profiles, :local)
323 end
324
325 defp restrict_unauthenticated?(%User{local: _}) do
326 Config.restrict_unauthenticated_access?(:profiles, :remote)
327 end
328
329 defp visible_account_status(user) do
330 status = account_status(user)
331
332 if status in [:active, :password_reset_pending] do
333 :visible
334 else
335 status
336 end
337 end
338
339 @spec superuser?(User.t()) :: boolean()
340 def superuser?(%User{local: true, is_admin: true}), do: true
341 def superuser?(%User{local: true, is_moderator: true}), do: true
342 def superuser?(_), do: false
343
344 @spec invisible?(User.t()) :: boolean()
345 def invisible?(%User{invisible: true}), do: true
346 def invisible?(_), do: false
347
348 def avatar_url(user, options \\ []) do
349 case user.avatar do
350 %{"url" => [%{"href" => href} | _]} ->
351 href
352
353 _ ->
354 unless options[:no_default] do
355 Config.get([:assets, :default_user_avatar], "#{Endpoint.url()}/images/avi.png")
356 end
357 end
358 end
359
360 def banner_url(user, options \\ []) do
361 case user.banner do
362 %{"url" => [%{"href" => href} | _]} -> href
363 _ -> !options[:no_default] && "#{Endpoint.url()}/images/banner.png"
364 end
365 end
366
367 # Should probably be renamed or removed
368 @spec ap_id(User.t()) :: String.t()
369 def ap_id(%User{nickname: nickname}), do: "#{Endpoint.url()}/users/#{nickname}"
370
371 @spec ap_followers(User.t()) :: String.t()
372 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
373 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
374
375 @spec ap_following(User.t()) :: String.t()
376 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
377 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
378
379 @spec ap_featured_collection(User.t()) :: String.t()
380 def ap_featured_collection(%User{featured_address: fa}) when is_binary(fa), do: fa
381
382 def ap_featured_collection(%User{} = user), do: "#{ap_id(user)}/collections/featured"
383
384 defp truncate_fields_param(params) do
385 if Map.has_key?(params, :fields) do
386 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
387 else
388 params
389 end
390 end
391
392 defp truncate_if_exists(params, key, max_length) do
393 if Map.has_key?(params, key) and is_binary(params[key]) do
394 {value, _chopped} = String.split_at(params[key], max_length)
395 Map.put(params, key, value)
396 else
397 params
398 end
399 end
400
401 defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
402
403 defp fix_follower_address(%{nickname: nickname} = params),
404 do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
405
406 defp fix_follower_address(params), do: params
407
408 def remote_user_changeset(struct \\ %User{local: false}, params) do
409 bio_limit = Config.get([:instance, :user_bio_length], 5000)
410 name_limit = Config.get([:instance, :user_name_length], 100)
411
412 name =
413 case params[:name] do
414 name when is_binary(name) and byte_size(name) > 0 -> name
415 _ -> params[:nickname]
416 end
417
418 params =
419 params
420 |> Map.put(:name, name)
421 |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
422 |> truncate_if_exists(:name, name_limit)
423 |> truncate_if_exists(:bio, bio_limit)
424 |> truncate_fields_param()
425 |> fix_follower_address()
426
427 struct
428 |> cast(
429 params,
430 [
431 :bio,
432 :emoji,
433 :ap_id,
434 :inbox,
435 :shared_inbox,
436 :nickname,
437 :public_key,
438 :avatar,
439 :ap_enabled,
440 :banner,
441 :is_locked,
442 :last_refreshed_at,
443 :uri,
444 :follower_address,
445 :following_address,
446 :featured_address,
447 :hide_followers,
448 :hide_follows,
449 :hide_followers_count,
450 :hide_follows_count,
451 :follower_count,
452 :fields,
453 :following_count,
454 :is_discoverable,
455 :invisible,
456 :actor_type,
457 :also_known_as,
458 :accepts_chat_messages,
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 :accepts_chat_messages,
520 :disclose_client
521 ]
522 )
523 |> unique_constraint(:nickname)
524 |> validate_format(:nickname, local_nickname_regex())
525 |> validate_length(:bio, max: bio_limit)
526 |> validate_length(:name, min: 1, max: name_limit)
527 |> validate_inclusion(:actor_type, ["Person", "Service"])
528 |> put_fields()
529 |> put_emoji()
530 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
531 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
532 |> put_change_if_present(:banner, &put_upload(&1, :banner))
533 |> put_change_if_present(:background, &put_upload(&1, :background))
534 |> put_change_if_present(
535 :pleroma_settings_store,
536 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
537 )
538 |> validate_fields(false)
539 end
540
541 defp put_fields(changeset) do
542 if raw_fields = get_change(changeset, :raw_fields) do
543 raw_fields =
544 raw_fields
545 |> Enum.filter(fn %{"name" => n} -> n != "" end)
546
547 fields =
548 raw_fields
549 |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
550
551 changeset
552 |> put_change(:raw_fields, raw_fields)
553 |> put_change(:fields, fields)
554 else
555 changeset
556 end
557 end
558
559 defp parse_fields(value) do
560 value
561 |> Formatter.linkify(mentions_format: :full)
562 |> elem(0)
563 end
564
565 defp put_emoji(changeset) do
566 emojified_fields = [:bio, :name, :raw_fields]
567
568 if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do
569 bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio))
570 name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name))
571
572 emoji = Map.merge(bio, name)
573
574 emoji =
575 changeset
576 |> get_field(:raw_fields)
577 |> Enum.reduce(emoji, fn x, acc ->
578 Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"]))
579 end)
580
581 put_change(changeset, :emoji, emoji)
582 else
583 changeset
584 end
585 end
586
587 defp put_change_if_present(changeset, map_field, value_function) do
588 with {:ok, value} <- fetch_change(changeset, map_field),
589 {:ok, new_value} <- value_function.(value) do
590 put_change(changeset, map_field, new_value)
591 else
592 _ -> changeset
593 end
594 end
595
596 defp put_upload(value, type) do
597 with %Plug.Upload{} <- value,
598 {:ok, object} <- ActivityPub.upload(value, type: type) do
599 {:ok, object.data}
600 end
601 end
602
603 def update_as_admin_changeset(struct, params) do
604 struct
605 |> update_changeset(params)
606 |> cast(params, [:email])
607 |> delete_change(:also_known_as)
608 |> unique_constraint(:email)
609 |> validate_format(:email, @email_regex)
610 |> validate_inclusion(:actor_type, ["Person", "Service"])
611 end
612
613 @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
614 def update_as_admin(user, params) do
615 params = Map.put(params, "password_confirmation", params["password"])
616 changeset = update_as_admin_changeset(user, params)
617
618 if params["password"] do
619 reset_password(user, changeset, params)
620 else
621 User.update_and_set_cache(changeset)
622 end
623 end
624
625 def password_update_changeset(struct, params) do
626 struct
627 |> cast(params, [:password, :password_confirmation])
628 |> validate_required([:password, :password_confirmation])
629 |> validate_confirmation(:password)
630 |> put_password_hash()
631 |> put_change(:password_reset_pending, false)
632 end
633
634 @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
635 def reset_password(%User{} = user, params) do
636 reset_password(user, user, params)
637 end
638
639 def reset_password(%User{id: user_id} = user, struct, params) do
640 multi =
641 Multi.new()
642 |> Multi.update(:user, password_update_changeset(struct, params))
643 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
644 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
645
646 case Repo.transaction(multi) do
647 {:ok, %{user: user} = _} -> set_cache(user)
648 {:error, _, changeset, _} -> {:error, changeset}
649 end
650 end
651
652 def update_password_reset_pending(user, value) do
653 user
654 |> change()
655 |> put_change(:password_reset_pending, value)
656 |> update_and_set_cache()
657 end
658
659 def force_password_reset_async(user) do
660 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
661 end
662
663 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
664 def force_password_reset(user), do: update_password_reset_pending(user, true)
665
666 # Used to auto-register LDAP accounts which won't have a password hash stored locally
667 def register_changeset_ldap(struct, params = %{password: password})
668 when is_nil(password) do
669 params = Map.put_new(params, :accepts_chat_messages, true)
670
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 :accepts_chat_messages
684 ])
685 |> validate_required([:name, :nickname])
686 |> unique_constraint(:nickname)
687 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
688 |> validate_format(:nickname, local_nickname_regex())
689 |> put_ap_id()
690 |> unique_constraint(:ap_id)
691 |> put_following_and_follower_and_featured_address()
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 params = Map.put_new(params, :accepts_chat_messages, true)
699
700 confirmed? =
701 if is_nil(opts[:confirmed]) do
702 !Config.get([:instance, :account_activation_required])
703 else
704 opts[:confirmed]
705 end
706
707 approved? =
708 if is_nil(opts[:approved]) do
709 !Config.get([:instance, :account_approval_required])
710 else
711 opts[:approved]
712 end
713
714 struct
715 |> confirmation_changeset(set_confirmation: confirmed?)
716 |> approval_changeset(set_approval: approved?)
717 |> cast(params, [
718 :bio,
719 :raw_bio,
720 :email,
721 :name,
722 :nickname,
723 :password,
724 :password_confirmation,
725 :emoji,
726 :accepts_chat_messages,
727 :registration_reason,
728 :language
729 ])
730 |> validate_required([:name, :nickname, :password, :password_confirmation])
731 |> validate_confirmation(:password)
732 |> unique_constraint(:email)
733 |> validate_format(:email, @email_regex)
734 |> validate_change(:email, fn :email, email ->
735 valid? =
736 Config.get([User, :email_blacklist])
737 |> Enum.all?(fn blacklisted_domain ->
738 !String.ends_with?(email, ["@" <> blacklisted_domain, "." <> blacklisted_domain])
739 end)
740
741 if valid?, do: [], else: [email: "Invalid email"]
742 end)
743 |> unique_constraint(:nickname)
744 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
745 |> validate_format(:nickname, local_nickname_regex())
746 |> validate_length(:bio, max: bio_limit)
747 |> validate_length(:name, min: 1, max: name_limit)
748 |> validate_length(:registration_reason, max: reason_limit)
749 |> maybe_validate_required_email(opts[:external])
750 |> put_password_hash
751 |> put_ap_id()
752 |> unique_constraint(:ap_id)
753 |> put_following_and_follower_and_featured_address()
754 end
755
756 def maybe_validate_required_email(changeset, true), do: changeset
757
758 def maybe_validate_required_email(changeset, _) do
759 if Config.get([:instance, :account_activation_required]) do
760 validate_required(changeset, [:email])
761 else
762 changeset
763 end
764 end
765
766 defp put_ap_id(changeset) do
767 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
768 put_change(changeset, :ap_id, ap_id)
769 end
770
771 defp put_following_and_follower_and_featured_address(changeset) do
772 user = %User{nickname: get_field(changeset, :nickname)}
773 followers = ap_followers(user)
774 following = ap_following(user)
775 featured = ap_featured_collection(user)
776
777 changeset
778 |> put_change(:follower_address, followers)
779 |> put_change(:following_address, following)
780 |> put_change(:featured_address, featured)
781 end
782
783 defp autofollow_users(user) do
784 candidates = Config.get([:instance, :autofollowed_nicknames])
785
786 autofollowed_users =
787 User.Query.build(%{nickname: candidates, local: true, is_active: true})
788 |> Repo.all()
789
790 follow_all(user, autofollowed_users)
791 end
792
793 defp autofollowing_users(user) do
794 candidates = Config.get([:instance, :autofollowing_nicknames])
795
796 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
797 |> Repo.all()
798 |> Enum.each(&follow(&1, user, :follow_accept))
799
800 {:ok, :success}
801 end
802
803 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
804 def register(%Ecto.Changeset{} = changeset) do
805 with {:ok, user} <- Repo.insert(changeset) do
806 post_register_action(user)
807 end
808 end
809
810 def post_register_action(%User{is_confirmed: false} = user) do
811 with {:ok, _} <- maybe_send_confirmation_email(user) do
812 {:ok, user}
813 end
814 end
815
816 def post_register_action(%User{is_approved: false} = user) do
817 with {:ok, _} <- send_user_approval_email(user),
818 {:ok, _} <- send_admin_approval_emails(user) do
819 {:ok, user}
820 end
821 end
822
823 def post_register_action(%User{is_approved: true, is_confirmed: true} = user) do
824 with {:ok, user} <- autofollow_users(user),
825 {:ok, _} <- autofollowing_users(user),
826 {:ok, user} <- set_cache(user),
827 {:ok, _} <- maybe_send_registration_email(user),
828 {:ok, _} <- maybe_send_welcome_email(user),
829 {:ok, _} <- maybe_send_welcome_message(user),
830 {:ok, _} <- maybe_send_welcome_chat_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_chat_message(user) do
865 if User.WelcomeChatMessage.enabled?() do
866 User.WelcomeChatMessage.post_message(user)
867 {:ok, :enqueued}
868 else
869 {:ok, :noop}
870 end
871 end
872
873 defp maybe_send_welcome_email(%User{email: email} = user) when is_binary(email) do
874 if User.WelcomeEmail.enabled?() do
875 User.WelcomeEmail.send_email(user)
876 {:ok, :enqueued}
877 else
878 {:ok, :noop}
879 end
880 end
881
882 defp maybe_send_welcome_email(_), do: {:ok, :noop}
883
884 @spec maybe_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
885 def maybe_send_confirmation_email(%User{is_confirmed: false, email: email} = user)
886 when is_binary(email) do
887 if Config.get([:instance, :account_activation_required]) do
888 send_confirmation_email(user)
889 {:ok, :enqueued}
890 else
891 {:ok, :noop}
892 end
893 end
894
895 def maybe_send_confirmation_email(_), do: {:ok, :noop}
896
897 @spec send_confirmation_email(Uset.t()) :: User.t()
898 def send_confirmation_email(%User{} = user) do
899 user
900 |> Pleroma.Emails.UserEmail.account_confirmation_email()
901 |> Pleroma.Emails.Mailer.deliver_async()
902
903 user
904 end
905
906 @spec maybe_send_registration_email(User.t()) :: {:ok, :enqueued | :noop}
907 defp maybe_send_registration_email(%User{email: email} = user) when is_binary(email) do
908 with false <- User.WelcomeEmail.enabled?(),
909 false <- Config.get([:instance, :account_activation_required], false),
910 false <- Config.get([:instance, :account_approval_required], false) do
911 user
912 |> Pleroma.Emails.UserEmail.successful_registration_email()
913 |> Pleroma.Emails.Mailer.deliver_async()
914
915 {:ok, :enqueued}
916 else
917 _ ->
918 {:ok, :noop}
919 end
920 end
921
922 defp maybe_send_registration_email(_), do: {:ok, :noop}
923
924 def needs_update?(%User{local: true}), do: false
925
926 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
927
928 def needs_update?(%User{local: false} = user) do
929 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
930 end
931
932 def needs_update?(_), do: true
933
934 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
935
936 # "Locked" (self-locked) users demand explicit authorization of follow requests
937 def maybe_direct_follow(%User{} = follower, %User{local: true, is_locked: true} = followed) do
938 follow(follower, followed, :follow_pending)
939 end
940
941 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
942 follow(follower, followed)
943 end
944
945 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
946 if not ap_enabled?(followed) do
947 follow(follower, followed)
948 else
949 {:ok, follower, followed}
950 end
951 end
952
953 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
954 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
955 def follow_all(follower, followeds) do
956 followeds
957 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
958 |> Enum.each(&follow(follower, &1, :follow_accept))
959
960 set_cache(follower)
961 end
962
963 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
964 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
965
966 cond do
967 not followed.is_active ->
968 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
969
970 deny_follow_blocked and blocks?(followed, follower) ->
971 {:error, "Could not follow user: #{followed.nickname} blocked you."}
972
973 true ->
974 FollowingRelationship.follow(follower, followed, state)
975 end
976 end
977
978 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
979 {:error, "Not subscribed!"}
980 end
981
982 @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
983 def unfollow(%User{} = follower, %User{} = followed) do
984 case do_unfollow(follower, followed) do
985 {:ok, follower, followed} ->
986 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
987
988 error ->
989 error
990 end
991 end
992
993 @spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()}
994 defp do_unfollow(%User{} = follower, %User{} = followed) do
995 case get_follow_state(follower, followed) do
996 state when state in [:follow_pending, :follow_accept] ->
997 FollowingRelationship.unfollow(follower, followed)
998
999 nil ->
1000 {:error, "Not subscribed!"}
1001 end
1002 end
1003
1004 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
1005 def get_follow_state(%User{} = follower, %User{} = following) do
1006 following_relationship = FollowingRelationship.get(follower, following)
1007 get_follow_state(follower, following, following_relationship)
1008 end
1009
1010 def get_follow_state(
1011 %User{} = follower,
1012 %User{} = following,
1013 following_relationship
1014 ) do
1015 case {following_relationship, following.local} do
1016 {nil, false} ->
1017 case Utils.fetch_latest_follow(follower, following) do
1018 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
1019 FollowingRelationship.state_to_enum(state)
1020
1021 _ ->
1022 nil
1023 end
1024
1025 {%{state: state}, _} ->
1026 state
1027
1028 {nil, _} ->
1029 nil
1030 end
1031 end
1032
1033 def locked?(%User{} = user) do
1034 user.is_locked || false
1035 end
1036
1037 def get_by_id(id) do
1038 Repo.get_by(User, id: id)
1039 end
1040
1041 def get_by_ap_id(ap_id) do
1042 Repo.get_by(User, ap_id: ap_id)
1043 end
1044
1045 def get_all_by_ap_id(ap_ids) do
1046 from(u in __MODULE__,
1047 where: u.ap_id in ^ap_ids
1048 )
1049 |> Repo.all()
1050 end
1051
1052 def get_all_by_ids(ids) do
1053 from(u in __MODULE__, where: u.id in ^ids)
1054 |> Repo.all()
1055 end
1056
1057 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
1058 # of the ap_id and the domain and tries to get that user
1059 def get_by_guessed_nickname(ap_id) do
1060 domain = URI.parse(ap_id).host
1061 name = List.last(String.split(ap_id, "/"))
1062 nickname = "#{name}@#{domain}"
1063
1064 get_cached_by_nickname(nickname)
1065 end
1066
1067 def set_cache({:ok, user}), do: set_cache(user)
1068 def set_cache({:error, err}), do: {:error, err}
1069
1070 def set_cache(%User{} = user) do
1071 @cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
1072 @cachex.put(:user_cache, "nickname:#{user.nickname}", user)
1073 @cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
1074 {:ok, user}
1075 end
1076
1077 def update_and_set_cache(struct, params) do
1078 struct
1079 |> update_changeset(params)
1080 |> update_and_set_cache()
1081 end
1082
1083 def update_and_set_cache(%{data: %Pleroma.User{} = user} = changeset) do
1084 was_superuser_before_update = User.superuser?(user)
1085
1086 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
1087 set_cache(user)
1088 end
1089 |> maybe_remove_report_notifications(was_superuser_before_update)
1090 end
1091
1092 defp maybe_remove_report_notifications({:ok, %Pleroma.User{} = user} = result, true) do
1093 if not User.superuser?(user),
1094 do: user |> Notification.destroy_multiple_from_types(["pleroma:report"])
1095
1096 result
1097 end
1098
1099 defp maybe_remove_report_notifications(result, _) do
1100 result
1101 end
1102
1103 def get_user_friends_ap_ids(user) do
1104 from(u in User.get_friends_query(user), select: u.ap_id)
1105 |> Repo.all()
1106 end
1107
1108 @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
1109 def get_cached_user_friends_ap_ids(user) do
1110 @cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
1111 get_user_friends_ap_ids(user)
1112 end)
1113 end
1114
1115 def invalidate_cache(user) do
1116 @cachex.del(:user_cache, "ap_id:#{user.ap_id}")
1117 @cachex.del(:user_cache, "nickname:#{user.nickname}")
1118 @cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
1119 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
1120 @cachex.del(:user_cache, "muted_users_ap_ids:#{user.ap_id}")
1121 end
1122
1123 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
1124 def get_cached_by_ap_id(ap_id) do
1125 key = "ap_id:#{ap_id}"
1126
1127 with {:ok, nil} <- @cachex.get(:user_cache, key),
1128 user when not is_nil(user) <- get_by_ap_id(ap_id),
1129 {:ok, true} <- @cachex.put(:user_cache, key, user) do
1130 user
1131 else
1132 {:ok, user} -> user
1133 nil -> nil
1134 end
1135 end
1136
1137 def get_cached_by_id(id) do
1138 key = "id:#{id}"
1139
1140 ap_id =
1141 @cachex.fetch!(:user_cache, key, fn _ ->
1142 user = get_by_id(id)
1143
1144 if user do
1145 @cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
1146 {:commit, user.ap_id}
1147 else
1148 {:ignore, ""}
1149 end
1150 end)
1151
1152 get_cached_by_ap_id(ap_id)
1153 end
1154
1155 def get_cached_by_nickname(nickname) do
1156 key = "nickname:#{nickname}"
1157
1158 @cachex.fetch!(:user_cache, key, fn _ ->
1159 case get_or_fetch_by_nickname(nickname) do
1160 {:ok, user} -> {:commit, user}
1161 {:error, _error} -> {:ignore, nil}
1162 end
1163 end)
1164 end
1165
1166 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
1167 restrict_to_local = Config.get([:instance, :limit_to_local_content])
1168
1169 cond do
1170 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
1171 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
1172
1173 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
1174 get_cached_by_nickname(nickname_or_id)
1175
1176 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
1177 get_cached_by_nickname(nickname_or_id)
1178
1179 true ->
1180 nil
1181 end
1182 end
1183
1184 @spec get_by_nickname(String.t()) :: User.t() | nil
1185 def get_by_nickname(nickname) do
1186 Repo.get_by(User, nickname: nickname) ||
1187 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
1188 Repo.get_by(User, nickname: local_nickname(nickname))
1189 end
1190 end
1191
1192 def get_by_email(email), do: Repo.get_by(User, email: email)
1193
1194 def get_by_nickname_or_email(nickname_or_email) do
1195 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
1196 end
1197
1198 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
1199
1200 def get_or_fetch_by_nickname(nickname) do
1201 with %User{} = user <- get_by_nickname(nickname) do
1202 {:ok, user}
1203 else
1204 _e ->
1205 with [_nick, _domain] <- String.split(nickname, "@"),
1206 {:ok, user} <- fetch_by_nickname(nickname) do
1207 {:ok, user}
1208 else
1209 _e -> {:error, "not found " <> nickname}
1210 end
1211 end
1212 end
1213
1214 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1215 def get_followers_query(%User{} = user, nil) do
1216 User.Query.build(%{followers: user, is_active: true})
1217 end
1218
1219 def get_followers_query(%User{} = user, page) do
1220 user
1221 |> get_followers_query(nil)
1222 |> User.Query.paginate(page, 20)
1223 end
1224
1225 @spec get_followers_query(User.t()) :: Ecto.Query.t()
1226 def get_followers_query(%User{} = user), do: get_followers_query(user, nil)
1227
1228 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1229 def get_followers(%User{} = user, page \\ nil) do
1230 user
1231 |> get_followers_query(page)
1232 |> Repo.all()
1233 end
1234
1235 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1236 def get_external_followers(%User{} = user, page \\ nil) do
1237 user
1238 |> get_followers_query(page)
1239 |> User.Query.build(%{external: true})
1240 |> Repo.all()
1241 end
1242
1243 def get_followers_ids(%User{} = user, page \\ nil) do
1244 user
1245 |> get_followers_query(page)
1246 |> select([u], u.id)
1247 |> Repo.all()
1248 end
1249
1250 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1251 def get_friends_query(%User{} = user, nil) do
1252 User.Query.build(%{friends: user, deactivated: false})
1253 end
1254
1255 def get_friends_query(%User{} = user, page) do
1256 user
1257 |> get_friends_query(nil)
1258 |> User.Query.paginate(page, 20)
1259 end
1260
1261 @spec get_friends_query(User.t()) :: Ecto.Query.t()
1262 def get_friends_query(%User{} = user), do: get_friends_query(user, nil)
1263
1264 def get_friends(%User{} = user, page \\ nil) do
1265 user
1266 |> get_friends_query(page)
1267 |> Repo.all()
1268 end
1269
1270 def get_friends_ap_ids(%User{} = user) do
1271 user
1272 |> get_friends_query(nil)
1273 |> select([u], u.ap_id)
1274 |> Repo.all()
1275 end
1276
1277 def get_friends_ids(%User{} = user, page \\ nil) do
1278 user
1279 |> get_friends_query(page)
1280 |> select([u], u.id)
1281 |> Repo.all()
1282 end
1283
1284 def increase_note_count(%User{} = user) do
1285 User
1286 |> where(id: ^user.id)
1287 |> update([u], inc: [note_count: 1])
1288 |> select([u], u)
1289 |> Repo.update_all([])
1290 |> case do
1291 {1, [user]} -> set_cache(user)
1292 _ -> {:error, user}
1293 end
1294 end
1295
1296 def decrease_note_count(%User{} = user) do
1297 User
1298 |> where(id: ^user.id)
1299 |> update([u],
1300 set: [
1301 note_count: fragment("greatest(0, note_count - 1)")
1302 ]
1303 )
1304 |> select([u], u)
1305 |> Repo.update_all([])
1306 |> case do
1307 {1, [user]} -> set_cache(user)
1308 _ -> {:error, user}
1309 end
1310 end
1311
1312 def update_note_count(%User{} = user, note_count \\ nil) do
1313 note_count =
1314 note_count ||
1315 from(
1316 a in Object,
1317 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1318 select: count(a.id)
1319 )
1320 |> Repo.one()
1321
1322 user
1323 |> cast(%{note_count: note_count}, [:note_count])
1324 |> update_and_set_cache()
1325 end
1326
1327 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1328 def maybe_fetch_follow_information(user) do
1329 with {:ok, user} <- fetch_follow_information(user) do
1330 user
1331 else
1332 e ->
1333 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1334
1335 user
1336 end
1337 end
1338
1339 def fetch_follow_information(user) do
1340 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1341 user
1342 |> follow_information_changeset(info)
1343 |> update_and_set_cache()
1344 end
1345 end
1346
1347 defp follow_information_changeset(user, params) do
1348 user
1349 |> cast(params, [
1350 :hide_followers,
1351 :hide_follows,
1352 :follower_count,
1353 :following_count,
1354 :hide_followers_count,
1355 :hide_follows_count
1356 ])
1357 end
1358
1359 @spec update_follower_count(User.t()) :: {:ok, User.t()}
1360 def update_follower_count(%User{} = user) do
1361 if user.local or !Config.get([:instance, :external_user_synchronization]) do
1362 follower_count = FollowingRelationship.follower_count(user)
1363
1364 user
1365 |> follow_information_changeset(%{follower_count: follower_count})
1366 |> update_and_set_cache
1367 else
1368 {:ok, maybe_fetch_follow_information(user)}
1369 end
1370 end
1371
1372 @spec update_following_count(User.t()) :: {:ok, User.t()}
1373 def update_following_count(%User{local: false} = user) do
1374 if Config.get([:instance, :external_user_synchronization]) do
1375 {:ok, maybe_fetch_follow_information(user)}
1376 else
1377 {:ok, user}
1378 end
1379 end
1380
1381 def update_following_count(%User{local: true} = user) do
1382 following_count = FollowingRelationship.following_count(user)
1383
1384 user
1385 |> follow_information_changeset(%{following_count: following_count})
1386 |> update_and_set_cache()
1387 end
1388
1389 @spec get_users_from_set([String.t()], keyword()) :: [User.t()]
1390 def get_users_from_set(ap_ids, opts \\ []) do
1391 local_only = Keyword.get(opts, :local_only, true)
1392 criteria = %{ap_id: ap_ids, is_active: true}
1393 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1394
1395 User.Query.build(criteria)
1396 |> Repo.all()
1397 end
1398
1399 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1400 def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
1401 to = [actor | to]
1402
1403 query = User.Query.build(%{recipients_from_activity: to, local: true, is_active: true})
1404
1405 query
1406 |> Repo.all()
1407 end
1408
1409 @spec mute(User.t(), User.t(), map()) ::
1410 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1411 def mute(%User{} = muter, %User{} = mutee, params \\ %{}) do
1412 notifications? = Map.get(params, :notifications, true)
1413 expires_in = Map.get(params, :expires_in, 0)
1414
1415 with {:ok, user_mute} <- UserRelationship.create_mute(muter, mutee),
1416 {:ok, user_notification_mute} <-
1417 (notifications? && UserRelationship.create_notification_mute(muter, mutee)) ||
1418 {:ok, nil} do
1419 if expires_in > 0 do
1420 Pleroma.Workers.MuteExpireWorker.enqueue(
1421 "unmute_user",
1422 %{"muter_id" => muter.id, "mutee_id" => mutee.id},
1423 schedule_in: expires_in
1424 )
1425 end
1426
1427 @cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
1428
1429 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
1430 end
1431 end
1432
1433 def unmute(%User{} = muter, %User{} = mutee) do
1434 with {:ok, user_mute} <- UserRelationship.delete_mute(muter, mutee),
1435 {:ok, user_notification_mute} <-
1436 UserRelationship.delete_notification_mute(muter, mutee) do
1437 @cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
1438 {:ok, [user_mute, user_notification_mute]}
1439 end
1440 end
1441
1442 def unmute(muter_id, mutee_id) do
1443 with {:muter, %User{} = muter} <- {:muter, User.get_by_id(muter_id)},
1444 {:mutee, %User{} = mutee} <- {:mutee, User.get_by_id(mutee_id)} do
1445 unmute(muter, mutee)
1446 else
1447 {who, result} = error ->
1448 Logger.warn(
1449 "User.unmute/2 failed. #{who}: #{result}, muter_id: #{muter_id}, mutee_id: #{mutee_id}"
1450 )
1451
1452 {:error, error}
1453 end
1454 end
1455
1456 def subscribe(%User{} = subscriber, %User{} = target) do
1457 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
1458
1459 if blocks?(target, subscriber) and deny_follow_blocked do
1460 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1461 else
1462 # Note: the relationship is inverse: subscriber acts as relationship target
1463 UserRelationship.create_inverse_subscription(target, subscriber)
1464 end
1465 end
1466
1467 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1468 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1469 subscribe(subscriber, subscribee)
1470 end
1471 end
1472
1473 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1474 # Note: the relationship is inverse: subscriber acts as relationship target
1475 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1476 end
1477
1478 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1479 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1480 unsubscribe(unsubscriber, user)
1481 end
1482 end
1483
1484 def block(%User{} = blocker, %User{} = blocked) do
1485 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1486 blocker =
1487 if following?(blocker, blocked) do
1488 {:ok, blocker, _} = unfollow(blocker, blocked)
1489 blocker
1490 else
1491 blocker
1492 end
1493
1494 # clear any requested follows as well
1495 blocked =
1496 case CommonAPI.reject_follow_request(blocked, blocker) do
1497 {:ok, %User{} = updated_blocked} -> updated_blocked
1498 nil -> blocked
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 _ ->
1917 {:error, :not_found}
1918 end
1919 end
1920
1921 @doc """
1922 Creates an internal service actor by URI if missing.
1923 Optionally takes nickname for addressing.
1924 """
1925 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1926 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1927 {_, user} =
1928 case get_cached_by_ap_id(uri) do
1929 nil ->
1930 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1931 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1932 {:error, nil}
1933 end
1934
1935 %User{invisible: false} = user ->
1936 set_invisible(user)
1937
1938 user ->
1939 {:ok, user}
1940 end
1941
1942 user
1943 end
1944
1945 @spec set_invisible(User.t()) :: {:ok, User.t()}
1946 defp set_invisible(user) do
1947 user
1948 |> change(%{invisible: true})
1949 |> update_and_set_cache()
1950 end
1951
1952 @spec create_service_actor(String.t(), String.t()) ::
1953 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1954 defp create_service_actor(uri, nickname) do
1955 %User{
1956 invisible: true,
1957 local: true,
1958 ap_id: uri,
1959 nickname: nickname,
1960 follower_address: uri <> "/followers"
1961 }
1962 |> change
1963 |> unique_constraint(:nickname)
1964 |> Repo.insert()
1965 |> set_cache()
1966 end
1967
1968 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1969 key =
1970 public_key_pem
1971 |> :public_key.pem_decode()
1972 |> hd()
1973 |> :public_key.pem_entry_decode()
1974
1975 {:ok, key}
1976 end
1977
1978 def public_key(_), do: {:error, "key not found"}
1979
1980 def get_public_key_for_ap_id(ap_id) do
1981 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1982 {:ok, public_key} <- public_key(user) do
1983 {:ok, public_key}
1984 else
1985 _ -> :error
1986 end
1987 end
1988
1989 def ap_enabled?(%User{local: true}), do: true
1990 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1991 def ap_enabled?(_), do: false
1992
1993 @doc "Gets or fetch a user by uri or nickname."
1994 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1995 def get_or_fetch("http" <> _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 defp 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 ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
2229
2230 def ensure_keys_present(%User{} = user) do
2231 with {:ok, pem} <- Keys.generate_rsa_pem() do
2232 user
2233 |> cast(%{keys: pem}, [:keys])
2234 |> validate_required([:keys])
2235 |> update_and_set_cache()
2236 end
2237 end
2238
2239 def get_ap_ids_by_nicknames(nicknames) do
2240 from(u in User,
2241 where: u.nickname in ^nicknames,
2242 select: u.ap_id
2243 )
2244 |> Repo.all()
2245 end
2246
2247 defp put_password_hash(
2248 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
2249 ) do
2250 change(changeset, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
2251 end
2252
2253 defp put_password_hash(changeset), do: changeset
2254
2255 def is_internal_user?(%User{nickname: nil}), do: true
2256 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
2257 def is_internal_user?(_), do: false
2258
2259 # A hack because user delete activities have a fake id for whatever reason
2260 # TODO: Get rid of this
2261 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
2262
2263 def get_delivered_users_by_object_id(object_id) do
2264 from(u in User,
2265 inner_join: delivery in assoc(u, :deliveries),
2266 where: delivery.object_id == ^object_id
2267 )
2268 |> Repo.all()
2269 end
2270
2271 def change_email(user, email) do
2272 user
2273 |> cast(%{email: email}, [:email])
2274 |> maybe_validate_required_email(false)
2275 |> unique_constraint(:email)
2276 |> validate_format(:email, @email_regex)
2277 |> update_and_set_cache()
2278 end
2279
2280 def alias_users(user) do
2281 user.also_known_as
2282 |> Enum.map(&User.get_cached_by_ap_id/1)
2283 |> Enum.filter(fn user -> user != nil end)
2284 end
2285
2286 def add_alias(user, new_alias_user) do
2287 current_aliases = user.also_known_as || []
2288 new_alias_ap_id = new_alias_user.ap_id
2289
2290 if new_alias_ap_id in current_aliases do
2291 {:ok, user}
2292 else
2293 user
2294 |> cast(%{also_known_as: current_aliases ++ [new_alias_ap_id]}, [:also_known_as])
2295 |> update_and_set_cache()
2296 end
2297 end
2298
2299 def delete_alias(user, alias_user) do
2300 current_aliases = user.also_known_as || []
2301 alias_ap_id = alias_user.ap_id
2302
2303 if alias_ap_id in current_aliases do
2304 user
2305 |> cast(%{also_known_as: current_aliases -- [alias_ap_id]}, [:also_known_as])
2306 |> update_and_set_cache()
2307 else
2308 {:error, :no_such_alias}
2309 end
2310 end
2311
2312 # Internal function; public one is `deactivate/2`
2313 defp set_activation_status(user, status) do
2314 user
2315 |> cast(%{is_active: status}, [:is_active])
2316 |> update_and_set_cache()
2317 end
2318
2319 def update_banner(user, banner) do
2320 user
2321 |> cast(%{banner: banner}, [:banner])
2322 |> update_and_set_cache()
2323 end
2324
2325 def update_background(user, background) do
2326 user
2327 |> cast(%{background: background}, [:background])
2328 |> update_and_set_cache()
2329 end
2330
2331 def validate_fields(changeset, remote? \\ false) do
2332 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2333 limit = Config.get([:instance, limit_name], 0)
2334
2335 changeset
2336 |> validate_length(:fields, max: limit)
2337 |> validate_change(:fields, fn :fields, fields ->
2338 if Enum.all?(fields, &valid_field?/1) do
2339 []
2340 else
2341 [fields: "invalid"]
2342 end
2343 end)
2344 end
2345
2346 defp valid_field?(%{"name" => name, "value" => value}) do
2347 name_limit = Config.get([:instance, :account_field_name_length], 255)
2348 value_limit = Config.get([:instance, :account_field_value_length], 255)
2349
2350 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2351 String.length(value) <= value_limit
2352 end
2353
2354 defp valid_field?(_), do: false
2355
2356 defp truncate_field(%{"name" => name, "value" => value}) do
2357 {name, _chopped} =
2358 String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
2359
2360 {value, _chopped} =
2361 String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
2362
2363 %{"name" => name, "value" => value}
2364 end
2365
2366 def admin_api_update(user, params) do
2367 user
2368 |> cast(params, [
2369 :is_moderator,
2370 :is_admin,
2371 :show_role
2372 ])
2373 |> update_and_set_cache()
2374 end
2375
2376 @doc "Signs user out of all applications"
2377 def global_sign_out(user) do
2378 OAuth.Authorization.delete_user_authorizations(user)
2379 OAuth.Token.delete_user_tokens(user)
2380 end
2381
2382 def mascot_update(user, url) do
2383 user
2384 |> cast(%{mascot: url}, [:mascot])
2385 |> validate_required([:mascot])
2386 |> update_and_set_cache()
2387 end
2388
2389 def mastodon_settings_update(user, settings) do
2390 user
2391 |> cast(%{mastofe_settings: settings}, [:mastofe_settings])
2392 |> validate_required([:mastofe_settings])
2393 |> update_and_set_cache()
2394 end
2395
2396 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2397 def confirmation_changeset(user, set_confirmation: confirmed?) do
2398 params =
2399 if confirmed? do
2400 %{
2401 is_confirmed: true,
2402 confirmation_token: nil
2403 }
2404 else
2405 %{
2406 is_confirmed: false,
2407 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2408 }
2409 end
2410
2411 cast(user, params, [:is_confirmed, :confirmation_token])
2412 end
2413
2414 @spec approval_changeset(User.t(), keyword()) :: Changeset.t()
2415 def approval_changeset(user, set_approval: approved?) do
2416 cast(user, %{is_approved: approved?}, [:is_approved])
2417 end
2418
2419 @spec add_pinned_object_id(User.t(), String.t()) :: {:ok, User.t()} | {:error, term()}
2420 def add_pinned_object_id(%User{} = user, object_id) do
2421 if !user.pinned_objects[object_id] do
2422 params = %{pinned_objects: Map.put(user.pinned_objects, object_id, NaiveDateTime.utc_now())}
2423
2424 user
2425 |> cast(params, [:pinned_objects])
2426 |> validate_change(:pinned_objects, fn :pinned_objects, pinned_objects ->
2427 max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
2428
2429 if Enum.count(pinned_objects) <= max_pinned_statuses do
2430 []
2431 else
2432 [pinned_objects: "You have already pinned the maximum number of statuses"]
2433 end
2434 end)
2435 else
2436 change(user)
2437 end
2438 |> update_and_set_cache()
2439 end
2440
2441 @spec remove_pinned_object_id(User.t(), String.t()) :: {:ok, t()} | {:error, term()}
2442 def remove_pinned_object_id(%User{} = user, object_id) do
2443 user
2444 |> cast(
2445 %{pinned_objects: Map.delete(user.pinned_objects, object_id)},
2446 [:pinned_objects]
2447 )
2448 |> update_and_set_cache()
2449 end
2450
2451 def update_email_notifications(user, settings) do
2452 email_notifications =
2453 user.email_notifications
2454 |> Map.merge(settings)
2455 |> Map.take(["digest"])
2456
2457 params = %{email_notifications: email_notifications}
2458 fields = [:email_notifications]
2459
2460 user
2461 |> cast(params, fields)
2462 |> validate_required(fields)
2463 |> update_and_set_cache()
2464 end
2465
2466 defp set_domain_blocks(user, domain_blocks) do
2467 params = %{domain_blocks: domain_blocks}
2468
2469 user
2470 |> cast(params, [:domain_blocks])
2471 |> validate_required([:domain_blocks])
2472 |> update_and_set_cache()
2473 end
2474
2475 def block_domain(user, domain_blocked) do
2476 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2477 end
2478
2479 def unblock_domain(user, domain_blocked) do
2480 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2481 end
2482
2483 @spec add_to_block(User.t(), User.t()) ::
2484 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2485 defp add_to_block(%User{} = user, %User{} = blocked) do
2486 with {:ok, relationship} <- UserRelationship.create_block(user, blocked) do
2487 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2488 {:ok, relationship}
2489 end
2490 end
2491
2492 @spec add_to_block(User.t(), User.t()) ::
2493 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2494 defp remove_from_block(%User{} = user, %User{} = blocked) do
2495 with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do
2496 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2497 {:ok, relationship}
2498 end
2499 end
2500
2501 def set_invisible(user, invisible) do
2502 params = %{invisible: invisible}
2503
2504 user
2505 |> cast(params, [:invisible])
2506 |> validate_required([:invisible])
2507 |> update_and_set_cache()
2508 end
2509
2510 def sanitize_html(%User{} = user) do
2511 sanitize_html(user, nil)
2512 end
2513
2514 # User data that mastodon isn't filtering (treated as plaintext):
2515 # - field name
2516 # - display name
2517 def sanitize_html(%User{} = user, filter) do
2518 fields =
2519 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2520 %{
2521 "name" => name,
2522 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2523 }
2524 end)
2525
2526 user
2527 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2528 |> Map.put(:fields, fields)
2529 end
2530
2531 def get_host(%User{ap_id: ap_id} = _user) do
2532 URI.parse(ap_id).host
2533 end
2534
2535 def update_last_active_at(%__MODULE__{local: true} = user) do
2536 user
2537 |> cast(%{last_active_at: NaiveDateTime.utc_now()}, [:last_active_at])
2538 |> update_and_set_cache()
2539 end
2540
2541 def active_user_count(days \\ 30) do
2542 active_after = Timex.shift(NaiveDateTime.utc_now(), days: -days)
2543
2544 __MODULE__
2545 |> where([u], u.last_active_at >= ^active_after)
2546 |> where([u], u.local == true)
2547 |> Repo.aggregate(:count)
2548 end
2549
2550 def update_last_status_at(user) do
2551 User
2552 |> where(id: ^user.id)
2553 |> update([u], set: [last_status_at: fragment("NOW()")])
2554 |> select([u], u)
2555 |> Repo.update_all([])
2556 |> case do
2557 {1, [user]} -> set_cache(user)
2558 _ -> {:error, user}
2559 end
2560 end
2561 end