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