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