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