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