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