9c3b46ae8b7aa328287419e290b486292d69157d
[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 bio = get_change(changeset, :bio)
542 name = get_change(changeset, :name)
543
544 if bio || name do
545 emoji = Map.merge(Emoji.Formatter.get_emoji_map(bio), Emoji.Formatter.get_emoji_map(name))
546 put_change(changeset, :emoji, emoji)
547 else
548 changeset
549 end
550 end
551
552 defp put_change_if_present(changeset, map_field, value_function) do
553 with {:ok, value} <- fetch_change(changeset, map_field),
554 {:ok, new_value} <- value_function.(value) do
555 put_change(changeset, map_field, new_value)
556 else
557 _ -> changeset
558 end
559 end
560
561 defp put_upload(value, type) do
562 with %Plug.Upload{} <- value,
563 {:ok, object} <- ActivityPub.upload(value, type: type) do
564 {:ok, object.data}
565 end
566 end
567
568 def update_as_admin_changeset(struct, params) do
569 struct
570 |> update_changeset(params)
571 |> cast(params, [:email])
572 |> delete_change(:also_known_as)
573 |> unique_constraint(:email)
574 |> validate_format(:email, @email_regex)
575 |> validate_inclusion(:actor_type, ["Person", "Service"])
576 end
577
578 @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
579 def update_as_admin(user, params) do
580 params = Map.put(params, "password_confirmation", params["password"])
581 changeset = update_as_admin_changeset(user, params)
582
583 if params["password"] do
584 reset_password(user, changeset, params)
585 else
586 User.update_and_set_cache(changeset)
587 end
588 end
589
590 def password_update_changeset(struct, params) do
591 struct
592 |> cast(params, [:password, :password_confirmation])
593 |> validate_required([:password, :password_confirmation])
594 |> validate_confirmation(:password)
595 |> put_password_hash()
596 |> put_change(:password_reset_pending, false)
597 end
598
599 @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
600 def reset_password(%User{} = user, params) do
601 reset_password(user, user, params)
602 end
603
604 def reset_password(%User{id: user_id} = user, struct, params) do
605 multi =
606 Multi.new()
607 |> Multi.update(:user, password_update_changeset(struct, params))
608 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
609 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
610
611 case Repo.transaction(multi) do
612 {:ok, %{user: user} = _} -> set_cache(user)
613 {:error, _, changeset, _} -> {:error, changeset}
614 end
615 end
616
617 def update_password_reset_pending(user, value) do
618 user
619 |> change()
620 |> put_change(:password_reset_pending, value)
621 |> update_and_set_cache()
622 end
623
624 def force_password_reset_async(user) do
625 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
626 end
627
628 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
629 def force_password_reset(user), do: update_password_reset_pending(user, true)
630
631 def register_changeset(struct, params \\ %{}, opts \\ []) do
632 bio_limit = Config.get([:instance, :user_bio_length], 5000)
633 name_limit = Config.get([:instance, :user_name_length], 100)
634 params = Map.put_new(params, :accepts_chat_messages, true)
635
636 need_confirmation? =
637 if is_nil(opts[:need_confirmation]) do
638 Config.get([:instance, :account_activation_required])
639 else
640 opts[:need_confirmation]
641 end
642
643 need_approval? =
644 if is_nil(opts[:need_approval]) do
645 Config.get([:instance, :account_approval_required])
646 else
647 opts[:need_approval]
648 end
649
650 struct
651 |> confirmation_changeset(need_confirmation: need_confirmation?)
652 |> approval_changeset(need_approval: need_approval?)
653 |> cast(params, [
654 :bio,
655 :raw_bio,
656 :email,
657 :name,
658 :nickname,
659 :password,
660 :password_confirmation,
661 :emoji,
662 :accepts_chat_messages,
663 :registration_reason
664 ])
665 |> validate_required([:name, :nickname, :password, :password_confirmation])
666 |> validate_confirmation(:password)
667 |> unique_constraint(:email)
668 |> unique_constraint(:nickname)
669 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
670 |> validate_format(:nickname, local_nickname_regex())
671 |> validate_format(:email, @email_regex)
672 |> validate_length(:bio, max: bio_limit)
673 |> validate_length(:name, min: 1, max: name_limit)
674 |> maybe_validate_required_email(opts[:external])
675 |> put_password_hash
676 |> put_ap_id()
677 |> unique_constraint(:ap_id)
678 |> put_following_and_follower_address()
679 end
680
681 def maybe_validate_required_email(changeset, true), do: changeset
682
683 def maybe_validate_required_email(changeset, _) do
684 if Config.get([:instance, :account_activation_required]) do
685 validate_required(changeset, [:email])
686 else
687 changeset
688 end
689 end
690
691 defp put_ap_id(changeset) do
692 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
693 put_change(changeset, :ap_id, ap_id)
694 end
695
696 defp put_following_and_follower_address(changeset) do
697 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
698
699 changeset
700 |> put_change(:follower_address, followers)
701 end
702
703 defp autofollow_users(user) do
704 candidates = Config.get([:instance, :autofollowed_nicknames])
705
706 autofollowed_users =
707 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
708 |> Repo.all()
709
710 follow_all(user, autofollowed_users)
711 end
712
713 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
714 def register(%Ecto.Changeset{} = changeset) do
715 with {:ok, user} <- Repo.insert(changeset) do
716 post_register_action(user)
717 end
718 end
719
720 def post_register_action(%User{} = user) do
721 with {:ok, user} <- autofollow_users(user),
722 {:ok, user} <- set_cache(user),
723 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
724 {:ok, _} <- try_send_confirmation_email(user) do
725 {:ok, user}
726 end
727 end
728
729 def try_send_confirmation_email(%User{} = user) do
730 if user.confirmation_pending &&
731 Config.get([:instance, :account_activation_required]) do
732 user
733 |> Pleroma.Emails.UserEmail.account_confirmation_email()
734 |> Pleroma.Emails.Mailer.deliver_async()
735
736 {:ok, :enqueued}
737 else
738 {:ok, :noop}
739 end
740 end
741
742 def try_send_confirmation_email(users) do
743 Enum.each(users, &try_send_confirmation_email/1)
744 end
745
746 def needs_update?(%User{local: true}), do: false
747
748 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
749
750 def needs_update?(%User{local: false} = user) do
751 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
752 end
753
754 def needs_update?(_), do: true
755
756 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
757
758 # "Locked" (self-locked) users demand explicit authorization of follow requests
759 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
760 follow(follower, followed, :follow_pending)
761 end
762
763 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
764 follow(follower, followed)
765 end
766
767 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
768 if not ap_enabled?(followed) do
769 follow(follower, followed)
770 else
771 {:ok, follower}
772 end
773 end
774
775 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
776 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
777 def follow_all(follower, followeds) do
778 followeds
779 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
780 |> Enum.each(&follow(follower, &1, :follow_accept))
781
782 set_cache(follower)
783 end
784
785 defdelegate following(user), to: FollowingRelationship
786
787 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
788 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
789
790 cond do
791 followed.deactivated ->
792 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
793
794 deny_follow_blocked and blocks?(followed, follower) ->
795 {:error, "Could not follow user: #{followed.nickname} blocked you."}
796
797 true ->
798 FollowingRelationship.follow(follower, followed, state)
799
800 {:ok, _} = update_follower_count(followed)
801
802 follower
803 |> update_following_count()
804 end
805 end
806
807 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
808 {:error, "Not subscribed!"}
809 end
810
811 @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
812 def unfollow(%User{} = follower, %User{} = followed) do
813 case do_unfollow(follower, followed) do
814 {:ok, follower, followed} ->
815 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
816
817 error ->
818 error
819 end
820 end
821
822 @spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()}
823 defp do_unfollow(%User{} = follower, %User{} = followed) do
824 case get_follow_state(follower, followed) do
825 state when state in [:follow_pending, :follow_accept] ->
826 FollowingRelationship.unfollow(follower, followed)
827 {:ok, followed} = update_follower_count(followed)
828
829 {:ok, follower} =
830 follower
831 |> update_following_count()
832
833 {:ok, follower, followed}
834
835 nil ->
836 {:error, "Not subscribed!"}
837 end
838 end
839
840 defdelegate following?(follower, followed), to: FollowingRelationship
841
842 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
843 def get_follow_state(%User{} = follower, %User{} = following) do
844 following_relationship = FollowingRelationship.get(follower, following)
845 get_follow_state(follower, following, following_relationship)
846 end
847
848 def get_follow_state(
849 %User{} = follower,
850 %User{} = following,
851 following_relationship
852 ) do
853 case {following_relationship, following.local} do
854 {nil, false} ->
855 case Utils.fetch_latest_follow(follower, following) do
856 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
857 FollowingRelationship.state_to_enum(state)
858
859 _ ->
860 nil
861 end
862
863 {%{state: state}, _} ->
864 state
865
866 {nil, _} ->
867 nil
868 end
869 end
870
871 def locked?(%User{} = user) do
872 user.locked || false
873 end
874
875 def get_by_id(id) do
876 Repo.get_by(User, id: id)
877 end
878
879 def get_by_ap_id(ap_id) do
880 Repo.get_by(User, ap_id: ap_id)
881 end
882
883 def get_all_by_ap_id(ap_ids) do
884 from(u in __MODULE__,
885 where: u.ap_id in ^ap_ids
886 )
887 |> Repo.all()
888 end
889
890 def get_all_by_ids(ids) do
891 from(u in __MODULE__, where: u.id in ^ids)
892 |> Repo.all()
893 end
894
895 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
896 # of the ap_id and the domain and tries to get that user
897 def get_by_guessed_nickname(ap_id) do
898 domain = URI.parse(ap_id).host
899 name = List.last(String.split(ap_id, "/"))
900 nickname = "#{name}@#{domain}"
901
902 get_cached_by_nickname(nickname)
903 end
904
905 def set_cache({:ok, user}), do: set_cache(user)
906 def set_cache({:error, err}), do: {:error, err}
907
908 def set_cache(%User{} = user) do
909 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
910 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
911 Cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
912 {:ok, user}
913 end
914
915 def update_and_set_cache(struct, params) do
916 struct
917 |> update_changeset(params)
918 |> update_and_set_cache()
919 end
920
921 def update_and_set_cache(changeset) do
922 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
923 set_cache(user)
924 end
925 end
926
927 def get_user_friends_ap_ids(user) do
928 from(u in User.get_friends_query(user), select: u.ap_id)
929 |> Repo.all()
930 end
931
932 @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
933 def get_cached_user_friends_ap_ids(user) do
934 Cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
935 get_user_friends_ap_ids(user)
936 end)
937 end
938
939 def invalidate_cache(user) do
940 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
941 Cachex.del(:user_cache, "nickname:#{user.nickname}")
942 Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
943 end
944
945 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
946 def get_cached_by_ap_id(ap_id) do
947 key = "ap_id:#{ap_id}"
948
949 with {:ok, nil} <- Cachex.get(:user_cache, key),
950 user when not is_nil(user) <- get_by_ap_id(ap_id),
951 {:ok, true} <- Cachex.put(:user_cache, key, user) do
952 user
953 else
954 {:ok, user} -> user
955 nil -> nil
956 end
957 end
958
959 def get_cached_by_id(id) do
960 key = "id:#{id}"
961
962 ap_id =
963 Cachex.fetch!(:user_cache, key, fn _ ->
964 user = get_by_id(id)
965
966 if user do
967 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
968 {:commit, user.ap_id}
969 else
970 {:ignore, ""}
971 end
972 end)
973
974 get_cached_by_ap_id(ap_id)
975 end
976
977 def get_cached_by_nickname(nickname) do
978 key = "nickname:#{nickname}"
979
980 Cachex.fetch!(:user_cache, key, fn ->
981 case get_or_fetch_by_nickname(nickname) do
982 {:ok, user} -> {:commit, user}
983 {:error, _error} -> {:ignore, nil}
984 end
985 end)
986 end
987
988 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
989 restrict_to_local = Config.get([:instance, :limit_to_local_content])
990
991 cond do
992 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
993 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
994
995 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
996 get_cached_by_nickname(nickname_or_id)
997
998 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
999 get_cached_by_nickname(nickname_or_id)
1000
1001 true ->
1002 nil
1003 end
1004 end
1005
1006 @spec get_by_nickname(String.t()) :: User.t() | nil
1007 def get_by_nickname(nickname) do
1008 Repo.get_by(User, nickname: nickname) ||
1009 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
1010 Repo.get_by(User, nickname: local_nickname(nickname))
1011 end
1012 end
1013
1014 def get_by_email(email), do: Repo.get_by(User, email: email)
1015
1016 def get_by_nickname_or_email(nickname_or_email) do
1017 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
1018 end
1019
1020 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
1021
1022 def get_or_fetch_by_nickname(nickname) do
1023 with %User{} = user <- get_by_nickname(nickname) do
1024 {:ok, user}
1025 else
1026 _e ->
1027 with [_nick, _domain] <- String.split(nickname, "@"),
1028 {:ok, user} <- fetch_by_nickname(nickname) do
1029 {:ok, user}
1030 else
1031 _e -> {:error, "not found " <> nickname}
1032 end
1033 end
1034 end
1035
1036 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1037 def get_followers_query(%User{} = user, nil) do
1038 User.Query.build(%{followers: user, deactivated: false})
1039 end
1040
1041 def get_followers_query(user, page) do
1042 user
1043 |> get_followers_query(nil)
1044 |> User.Query.paginate(page, 20)
1045 end
1046
1047 @spec get_followers_query(User.t()) :: Ecto.Query.t()
1048 def get_followers_query(user), do: get_followers_query(user, nil)
1049
1050 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1051 def get_followers(user, page \\ nil) do
1052 user
1053 |> get_followers_query(page)
1054 |> Repo.all()
1055 end
1056
1057 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1058 def get_external_followers(user, page \\ nil) do
1059 user
1060 |> get_followers_query(page)
1061 |> User.Query.build(%{external: true})
1062 |> Repo.all()
1063 end
1064
1065 def get_followers_ids(user, page \\ nil) do
1066 user
1067 |> get_followers_query(page)
1068 |> select([u], u.id)
1069 |> Repo.all()
1070 end
1071
1072 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1073 def get_friends_query(%User{} = user, nil) do
1074 User.Query.build(%{friends: user, deactivated: false})
1075 end
1076
1077 def get_friends_query(user, page) do
1078 user
1079 |> get_friends_query(nil)
1080 |> User.Query.paginate(page, 20)
1081 end
1082
1083 @spec get_friends_query(User.t()) :: Ecto.Query.t()
1084 def get_friends_query(user), do: get_friends_query(user, nil)
1085
1086 def get_friends(user, page \\ nil) do
1087 user
1088 |> get_friends_query(page)
1089 |> Repo.all()
1090 end
1091
1092 def get_friends_ap_ids(user) do
1093 user
1094 |> get_friends_query(nil)
1095 |> select([u], u.ap_id)
1096 |> Repo.all()
1097 end
1098
1099 def get_friends_ids(user, page \\ nil) do
1100 user
1101 |> get_friends_query(page)
1102 |> select([u], u.id)
1103 |> Repo.all()
1104 end
1105
1106 defdelegate get_follow_requests(user), to: FollowingRelationship
1107
1108 def increase_note_count(%User{} = user) do
1109 User
1110 |> where(id: ^user.id)
1111 |> update([u], inc: [note_count: 1])
1112 |> select([u], u)
1113 |> Repo.update_all([])
1114 |> case do
1115 {1, [user]} -> set_cache(user)
1116 _ -> {:error, user}
1117 end
1118 end
1119
1120 def decrease_note_count(%User{} = user) do
1121 User
1122 |> where(id: ^user.id)
1123 |> update([u],
1124 set: [
1125 note_count: fragment("greatest(0, note_count - 1)")
1126 ]
1127 )
1128 |> select([u], u)
1129 |> Repo.update_all([])
1130 |> case do
1131 {1, [user]} -> set_cache(user)
1132 _ -> {:error, user}
1133 end
1134 end
1135
1136 def update_note_count(%User{} = user, note_count \\ nil) do
1137 note_count =
1138 note_count ||
1139 from(
1140 a in Object,
1141 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1142 select: count(a.id)
1143 )
1144 |> Repo.one()
1145
1146 user
1147 |> cast(%{note_count: note_count}, [:note_count])
1148 |> update_and_set_cache()
1149 end
1150
1151 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1152 def maybe_fetch_follow_information(user) do
1153 with {:ok, user} <- fetch_follow_information(user) do
1154 user
1155 else
1156 e ->
1157 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1158
1159 user
1160 end
1161 end
1162
1163 def fetch_follow_information(user) do
1164 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1165 user
1166 |> follow_information_changeset(info)
1167 |> update_and_set_cache()
1168 end
1169 end
1170
1171 defp follow_information_changeset(user, params) do
1172 user
1173 |> cast(params, [
1174 :hide_followers,
1175 :hide_follows,
1176 :follower_count,
1177 :following_count,
1178 :hide_followers_count,
1179 :hide_follows_count
1180 ])
1181 end
1182
1183 @spec update_follower_count(User.t()) :: {:ok, User.t()}
1184 def update_follower_count(%User{} = user) do
1185 if user.local or !Config.get([:instance, :external_user_synchronization]) do
1186 follower_count = FollowingRelationship.follower_count(user)
1187
1188 user
1189 |> follow_information_changeset(%{follower_count: follower_count})
1190 |> update_and_set_cache
1191 else
1192 {:ok, maybe_fetch_follow_information(user)}
1193 end
1194 end
1195
1196 @spec update_following_count(User.t()) :: {:ok, User.t()}
1197 def update_following_count(%User{local: false} = user) do
1198 if Config.get([:instance, :external_user_synchronization]) do
1199 {:ok, maybe_fetch_follow_information(user)}
1200 else
1201 {:ok, user}
1202 end
1203 end
1204
1205 def update_following_count(%User{local: true} = user) do
1206 following_count = FollowingRelationship.following_count(user)
1207
1208 user
1209 |> follow_information_changeset(%{following_count: following_count})
1210 |> update_and_set_cache()
1211 end
1212
1213 def set_unread_conversation_count(%User{local: true} = user) do
1214 unread_query = Participation.unread_conversation_count_for_user(user)
1215
1216 User
1217 |> join(:inner, [u], p in subquery(unread_query))
1218 |> update([u, p],
1219 set: [unread_conversation_count: p.count]
1220 )
1221 |> where([u], u.id == ^user.id)
1222 |> select([u], u)
1223 |> Repo.update_all([])
1224 |> case do
1225 {1, [user]} -> set_cache(user)
1226 _ -> {:error, user}
1227 end
1228 end
1229
1230 def set_unread_conversation_count(user), do: {:ok, user}
1231
1232 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1233 unread_query =
1234 Participation.unread_conversation_count_for_user(user)
1235 |> where([p], p.conversation_id == ^conversation.id)
1236
1237 User
1238 |> join(:inner, [u], p in subquery(unread_query))
1239 |> update([u, p],
1240 inc: [unread_conversation_count: 1]
1241 )
1242 |> where([u], u.id == ^user.id)
1243 |> where([u, p], p.count == 0)
1244 |> select([u], u)
1245 |> Repo.update_all([])
1246 |> case do
1247 {1, [user]} -> set_cache(user)
1248 _ -> {:error, user}
1249 end
1250 end
1251
1252 def increment_unread_conversation_count(_, user), do: {:ok, user}
1253
1254 @spec get_users_from_set([String.t()], keyword()) :: [User.t()]
1255 def get_users_from_set(ap_ids, opts \\ []) do
1256 local_only = Keyword.get(opts, :local_only, true)
1257 criteria = %{ap_id: ap_ids, deactivated: false}
1258 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1259
1260 User.Query.build(criteria)
1261 |> Repo.all()
1262 end
1263
1264 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1265 def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
1266 to = [actor | to]
1267
1268 query = User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1269
1270 query
1271 |> Repo.all()
1272 end
1273
1274 @spec mute(User.t(), User.t(), boolean()) ::
1275 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1276 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1277 add_to_mutes(muter, mutee, notifications?)
1278 end
1279
1280 def unmute(%User{} = muter, %User{} = mutee) do
1281 remove_from_mutes(muter, mutee)
1282 end
1283
1284 def subscribe(%User{} = subscriber, %User{} = target) do
1285 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
1286
1287 if blocks?(target, subscriber) and deny_follow_blocked do
1288 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1289 else
1290 # Note: the relationship is inverse: subscriber acts as relationship target
1291 UserRelationship.create_inverse_subscription(target, subscriber)
1292 end
1293 end
1294
1295 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1296 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1297 subscribe(subscriber, subscribee)
1298 end
1299 end
1300
1301 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1302 # Note: the relationship is inverse: subscriber acts as relationship target
1303 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1304 end
1305
1306 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1307 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1308 unsubscribe(unsubscriber, user)
1309 end
1310 end
1311
1312 def block(%User{} = blocker, %User{} = blocked) do
1313 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1314 blocker =
1315 if following?(blocker, blocked) do
1316 {:ok, blocker, _} = unfollow(blocker, blocked)
1317 blocker
1318 else
1319 blocker
1320 end
1321
1322 # clear any requested follows as well
1323 blocked =
1324 case CommonAPI.reject_follow_request(blocked, blocker) do
1325 {:ok, %User{} = updated_blocked} -> updated_blocked
1326 nil -> blocked
1327 end
1328
1329 unsubscribe(blocked, blocker)
1330
1331 unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
1332 if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker)
1333
1334 {:ok, blocker} = update_follower_count(blocker)
1335 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1336 add_to_block(blocker, blocked)
1337 end
1338
1339 # helper to handle the block given only an actor's AP id
1340 def block(%User{} = blocker, %{ap_id: ap_id}) do
1341 block(blocker, get_cached_by_ap_id(ap_id))
1342 end
1343
1344 def unblock(%User{} = blocker, %User{} = blocked) do
1345 remove_from_block(blocker, blocked)
1346 end
1347
1348 # helper to handle the block given only an actor's AP id
1349 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1350 unblock(blocker, get_cached_by_ap_id(ap_id))
1351 end
1352
1353 def mutes?(nil, _), do: false
1354 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1355
1356 def mutes_user?(%User{} = user, %User{} = target) do
1357 UserRelationship.mute_exists?(user, target)
1358 end
1359
1360 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1361 def muted_notifications?(nil, _), do: false
1362
1363 def muted_notifications?(%User{} = user, %User{} = target),
1364 do: UserRelationship.notification_mute_exists?(user, target)
1365
1366 def blocks?(nil, _), do: false
1367
1368 def blocks?(%User{} = user, %User{} = target) do
1369 blocks_user?(user, target) ||
1370 (blocks_domain?(user, target) and not User.following?(user, target))
1371 end
1372
1373 def blocks_user?(%User{} = user, %User{} = target) do
1374 UserRelationship.block_exists?(user, target)
1375 end
1376
1377 def blocks_user?(_, _), do: false
1378
1379 def blocks_domain?(%User{} = user, %User{} = target) do
1380 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1381 %{host: host} = URI.parse(target.ap_id)
1382 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1383 end
1384
1385 def blocks_domain?(_, _), do: false
1386
1387 def subscribed_to?(%User{} = user, %User{} = target) do
1388 # Note: the relationship is inverse: subscriber acts as relationship target
1389 UserRelationship.inverse_subscription_exists?(target, user)
1390 end
1391
1392 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1393 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1394 subscribed_to?(user, target)
1395 end
1396 end
1397
1398 @doc """
1399 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1400 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1401 """
1402 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1403 def outgoing_relationships_ap_ids(_user, []), do: %{}
1404
1405 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1406
1407 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1408 when is_list(relationship_types) do
1409 db_result =
1410 user
1411 |> assoc(:outgoing_relationships)
1412 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1413 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1414 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1415 |> group_by([user_rel, u], user_rel.relationship_type)
1416 |> Repo.all()
1417 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1418
1419 Enum.into(
1420 relationship_types,
1421 %{},
1422 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1423 )
1424 end
1425
1426 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1427
1428 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1429
1430 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1431
1432 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1433 when is_list(relationship_types) do
1434 user
1435 |> assoc(:incoming_relationships)
1436 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1437 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1438 |> maybe_filter_on_ap_id(ap_ids)
1439 |> select([user_rel, u], u.ap_id)
1440 |> distinct(true)
1441 |> Repo.all()
1442 end
1443
1444 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1445 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1446 end
1447
1448 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1449
1450 def deactivate_async(user, status \\ true) do
1451 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1452 end
1453
1454 def deactivate(user, status \\ true)
1455
1456 def deactivate(users, status) when is_list(users) do
1457 Repo.transaction(fn ->
1458 for user <- users, do: deactivate(user, status)
1459 end)
1460 end
1461
1462 def deactivate(%User{} = user, status) do
1463 with {:ok, user} <- set_activation_status(user, status) do
1464 user
1465 |> get_followers()
1466 |> Enum.filter(& &1.local)
1467 |> Enum.each(&set_cache(update_following_count(&1)))
1468
1469 # Only update local user counts, remote will be update during the next pull.
1470 user
1471 |> get_friends()
1472 |> Enum.filter(& &1.local)
1473 |> Enum.each(&do_unfollow(user, &1))
1474
1475 {:ok, user}
1476 end
1477 end
1478
1479 def approve(users) when is_list(users) do
1480 Repo.transaction(fn ->
1481 Enum.map(users, fn user ->
1482 with {:ok, user} <- approve(user), do: user
1483 end)
1484 end)
1485 end
1486
1487 def approve(%User{} = user) do
1488 change(user, approval_pending: false)
1489 |> update_and_set_cache()
1490 end
1491
1492 def update_notification_settings(%User{} = user, settings) do
1493 user
1494 |> cast(%{notification_settings: settings}, [])
1495 |> cast_embed(:notification_settings)
1496 |> validate_required([:notification_settings])
1497 |> update_and_set_cache()
1498 end
1499
1500 def delete(users) when is_list(users) do
1501 for user <- users, do: delete(user)
1502 end
1503
1504 def delete(%User{} = user) do
1505 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1506 end
1507
1508 defp delete_and_invalidate_cache(%User{} = user) do
1509 invalidate_cache(user)
1510 Repo.delete(user)
1511 end
1512
1513 defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user)
1514
1515 defp delete_or_deactivate(%User{local: true} = user) do
1516 status = account_status(user)
1517
1518 if status == :confirmation_pending do
1519 delete_and_invalidate_cache(user)
1520 else
1521 user
1522 |> change(%{deactivated: true, email: nil})
1523 |> update_and_set_cache()
1524 end
1525 end
1526
1527 def perform(:force_password_reset, user), do: force_password_reset(user)
1528
1529 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1530 def perform(:delete, %User{} = user) do
1531 # Remove all relationships
1532 user
1533 |> get_followers()
1534 |> Enum.each(fn follower ->
1535 ActivityPub.unfollow(follower, user)
1536 unfollow(follower, user)
1537 end)
1538
1539 user
1540 |> get_friends()
1541 |> Enum.each(fn followed ->
1542 ActivityPub.unfollow(user, followed)
1543 unfollow(user, followed)
1544 end)
1545
1546 delete_user_activities(user)
1547 delete_notifications_from_user_activities(user)
1548
1549 delete_outgoing_pending_follow_requests(user)
1550
1551 delete_or_deactivate(user)
1552 end
1553
1554 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1555
1556 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1557 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1558 when is_list(blocked_identifiers) do
1559 Enum.map(
1560 blocked_identifiers,
1561 fn blocked_identifier ->
1562 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1563 {:ok, _block} <- CommonAPI.block(blocker, blocked) do
1564 blocked
1565 else
1566 err ->
1567 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1568 err
1569 end
1570 end
1571 )
1572 end
1573
1574 def perform(:follow_import, %User{} = follower, followed_identifiers)
1575 when is_list(followed_identifiers) do
1576 Enum.map(
1577 followed_identifiers,
1578 fn followed_identifier ->
1579 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1580 {:ok, follower} <- maybe_direct_follow(follower, followed),
1581 {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do
1582 followed
1583 else
1584 err ->
1585 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1586 err
1587 end
1588 end
1589 )
1590 end
1591
1592 @spec external_users_query() :: Ecto.Query.t()
1593 def external_users_query do
1594 User.Query.build(%{
1595 external: true,
1596 active: true,
1597 order_by: :id
1598 })
1599 end
1600
1601 @spec external_users(keyword()) :: [User.t()]
1602 def external_users(opts \\ []) do
1603 query =
1604 external_users_query()
1605 |> select([u], struct(u, [:id, :ap_id]))
1606
1607 query =
1608 if opts[:max_id],
1609 do: where(query, [u], u.id > ^opts[:max_id]),
1610 else: query
1611
1612 query =
1613 if opts[:limit],
1614 do: limit(query, ^opts[:limit]),
1615 else: query
1616
1617 Repo.all(query)
1618 end
1619
1620 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1621 BackgroundWorker.enqueue("blocks_import", %{
1622 "blocker_id" => blocker.id,
1623 "blocked_identifiers" => blocked_identifiers
1624 })
1625 end
1626
1627 def follow_import(%User{} = follower, followed_identifiers)
1628 when is_list(followed_identifiers) do
1629 BackgroundWorker.enqueue("follow_import", %{
1630 "follower_id" => follower.id,
1631 "followed_identifiers" => followed_identifiers
1632 })
1633 end
1634
1635 def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
1636 Notification
1637 |> join(:inner, [n], activity in assoc(n, :activity))
1638 |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
1639 |> Repo.delete_all()
1640 end
1641
1642 def delete_user_activities(%User{ap_id: ap_id} = user) do
1643 ap_id
1644 |> Activity.Queries.by_actor()
1645 |> RepoStreamer.chunk_stream(50)
1646 |> Stream.each(fn activities ->
1647 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
1648 end)
1649 |> Stream.run()
1650 end
1651
1652 defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
1653 with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
1654 {:ok, delete_data, _} <- Builder.delete(user, object) do
1655 Pipeline.common_pipeline(delete_data, local: user.local)
1656 else
1657 {:find_object, nil} ->
1658 # We have the create activity, but not the object, it was probably pruned.
1659 # Insert a tombstone and try again
1660 with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
1661 {:ok, _tombstone} <- Object.create(tombstone_data) do
1662 delete_activity(activity, user)
1663 end
1664
1665 e ->
1666 Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
1667 Logger.error("Error: #{inspect(e)}")
1668 end
1669 end
1670
1671 defp delete_activity(%{data: %{"type" => type}} = activity, user)
1672 when type in ["Like", "Announce"] do
1673 {:ok, undo, _} = Builder.undo(user, activity)
1674 Pipeline.common_pipeline(undo, local: user.local)
1675 end
1676
1677 defp delete_activity(_activity, _user), do: "Doing nothing"
1678
1679 defp delete_outgoing_pending_follow_requests(user) do
1680 user
1681 |> FollowingRelationship.outgoing_pending_follow_requests_query()
1682 |> Repo.delete_all()
1683 end
1684
1685 def html_filter_policy(%User{no_rich_text: true}) do
1686 Pleroma.HTML.Scrubber.TwitterText
1687 end
1688
1689 def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
1690
1691 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1692
1693 def get_or_fetch_by_ap_id(ap_id) do
1694 cached_user = get_cached_by_ap_id(ap_id)
1695
1696 maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
1697
1698 case {cached_user, maybe_fetched_user} do
1699 {_, {:ok, %User{} = user}} ->
1700 {:ok, user}
1701
1702 {%User{} = user, _} ->
1703 {:ok, user}
1704
1705 _ ->
1706 {:error, :not_found}
1707 end
1708 end
1709
1710 @doc """
1711 Creates an internal service actor by URI if missing.
1712 Optionally takes nickname for addressing.
1713 """
1714 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1715 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1716 {_, user} =
1717 case get_cached_by_ap_id(uri) do
1718 nil ->
1719 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1720 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1721 {:error, nil}
1722 end
1723
1724 %User{invisible: false} = user ->
1725 set_invisible(user)
1726
1727 user ->
1728 {:ok, user}
1729 end
1730
1731 user
1732 end
1733
1734 @spec set_invisible(User.t()) :: {:ok, User.t()}
1735 defp set_invisible(user) do
1736 user
1737 |> change(%{invisible: true})
1738 |> update_and_set_cache()
1739 end
1740
1741 @spec create_service_actor(String.t(), String.t()) ::
1742 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1743 defp create_service_actor(uri, nickname) do
1744 %User{
1745 invisible: true,
1746 local: true,
1747 ap_id: uri,
1748 nickname: nickname,
1749 follower_address: uri <> "/followers"
1750 }
1751 |> change
1752 |> unique_constraint(:nickname)
1753 |> Repo.insert()
1754 |> set_cache()
1755 end
1756
1757 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1758 key =
1759 public_key_pem
1760 |> :public_key.pem_decode()
1761 |> hd()
1762 |> :public_key.pem_entry_decode()
1763
1764 {:ok, key}
1765 end
1766
1767 def public_key(_), do: {:error, "key not found"}
1768
1769 def get_public_key_for_ap_id(ap_id) do
1770 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1771 {:ok, public_key} <- public_key(user) do
1772 {:ok, public_key}
1773 else
1774 _ -> :error
1775 end
1776 end
1777
1778 def ap_enabled?(%User{local: true}), do: true
1779 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1780 def ap_enabled?(_), do: false
1781
1782 @doc "Gets or fetch a user by uri or nickname."
1783 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1784 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1785 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1786
1787 # wait a period of time and return newest version of the User structs
1788 # this is because we have synchronous follow APIs and need to simulate them
1789 # with an async handshake
1790 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1791 with %User{} = a <- get_cached_by_id(a.id),
1792 %User{} = b <- get_cached_by_id(b.id) do
1793 {:ok, a, b}
1794 else
1795 nil -> :error
1796 end
1797 end
1798
1799 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1800 with :ok <- :timer.sleep(timeout),
1801 %User{} = a <- get_cached_by_id(a.id),
1802 %User{} = b <- get_cached_by_id(b.id) do
1803 {:ok, a, b}
1804 else
1805 nil -> :error
1806 end
1807 end
1808
1809 def parse_bio(bio) when is_binary(bio) and bio != "" do
1810 bio
1811 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1812 |> elem(0)
1813 end
1814
1815 def parse_bio(_), do: ""
1816
1817 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1818 # TODO: get profile URLs other than user.ap_id
1819 profile_urls = [user.ap_id]
1820
1821 bio
1822 |> CommonUtils.format_input("text/plain",
1823 mentions_format: :full,
1824 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1825 )
1826 |> elem(0)
1827 end
1828
1829 def parse_bio(_, _), do: ""
1830
1831 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1832 Repo.transaction(fn ->
1833 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1834 end)
1835 end
1836
1837 def tag(nickname, tags) when is_binary(nickname),
1838 do: tag(get_by_nickname(nickname), tags)
1839
1840 def tag(%User{} = user, tags),
1841 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1842
1843 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1844 Repo.transaction(fn ->
1845 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1846 end)
1847 end
1848
1849 def untag(nickname, tags) when is_binary(nickname),
1850 do: untag(get_by_nickname(nickname), tags)
1851
1852 def untag(%User{} = user, tags),
1853 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1854
1855 defp update_tags(%User{} = user, new_tags) do
1856 {:ok, updated_user} =
1857 user
1858 |> change(%{tags: new_tags})
1859 |> update_and_set_cache()
1860
1861 updated_user
1862 end
1863
1864 defp normalize_tags(tags) do
1865 [tags]
1866 |> List.flatten()
1867 |> Enum.map(&String.downcase/1)
1868 end
1869
1870 defp local_nickname_regex do
1871 if Config.get([:instance, :extended_nickname_format]) do
1872 @extended_local_nickname_regex
1873 else
1874 @strict_local_nickname_regex
1875 end
1876 end
1877
1878 def local_nickname(nickname_or_mention) do
1879 nickname_or_mention
1880 |> full_nickname()
1881 |> String.split("@")
1882 |> hd()
1883 end
1884
1885 def full_nickname(nickname_or_mention),
1886 do: String.trim_leading(nickname_or_mention, "@")
1887
1888 def error_user(ap_id) do
1889 %User{
1890 name: ap_id,
1891 ap_id: ap_id,
1892 nickname: "erroruser@example.com",
1893 inserted_at: NaiveDateTime.utc_now()
1894 }
1895 end
1896
1897 @spec all_superusers() :: [User.t()]
1898 def all_superusers do
1899 User.Query.build(%{super_users: true, local: true, deactivated: false})
1900 |> Repo.all()
1901 end
1902
1903 def muting_reblogs?(%User{} = user, %User{} = target) do
1904 UserRelationship.reblog_mute_exists?(user, target)
1905 end
1906
1907 def showing_reblogs?(%User{} = user, %User{} = target) do
1908 not muting_reblogs?(user, target)
1909 end
1910
1911 @doc """
1912 The function returns a query to get users with no activity for given interval of days.
1913 Inactive users are those who didn't read any notification, or had any activity where
1914 the user is the activity's actor, during `inactivity_threshold` days.
1915 Deactivated users will not appear in this list.
1916
1917 ## Examples
1918
1919 iex> Pleroma.User.list_inactive_users()
1920 %Ecto.Query{}
1921 """
1922 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1923 def list_inactive_users_query(inactivity_threshold \\ 7) do
1924 negative_inactivity_threshold = -inactivity_threshold
1925 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1926 # Subqueries are not supported in `where` clauses, join gets too complicated.
1927 has_read_notifications =
1928 from(n in Pleroma.Notification,
1929 where: n.seen == true,
1930 group_by: n.id,
1931 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1932 select: n.user_id
1933 )
1934 |> Pleroma.Repo.all()
1935
1936 from(u in Pleroma.User,
1937 left_join: a in Pleroma.Activity,
1938 on: u.ap_id == a.actor,
1939 where: not is_nil(u.nickname),
1940 where: u.deactivated != ^true,
1941 where: u.id not in ^has_read_notifications,
1942 group_by: u.id,
1943 having:
1944 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1945 is_nil(max(a.inserted_at))
1946 )
1947 end
1948
1949 @doc """
1950 Enable or disable email notifications for user
1951
1952 ## Examples
1953
1954 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1955 Pleroma.User{email_notifications: %{"digest" => true}}
1956
1957 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1958 Pleroma.User{email_notifications: %{"digest" => false}}
1959 """
1960 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1961 {:ok, t()} | {:error, Ecto.Changeset.t()}
1962 def switch_email_notifications(user, type, status) do
1963 User.update_email_notifications(user, %{type => status})
1964 end
1965
1966 @doc """
1967 Set `last_digest_emailed_at` value for the user to current time
1968 """
1969 @spec touch_last_digest_emailed_at(t()) :: t()
1970 def touch_last_digest_emailed_at(user) do
1971 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1972
1973 {:ok, updated_user} =
1974 user
1975 |> change(%{last_digest_emailed_at: now})
1976 |> update_and_set_cache()
1977
1978 updated_user
1979 end
1980
1981 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1982 def toggle_confirmation(%User{} = user) do
1983 user
1984 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1985 |> update_and_set_cache()
1986 end
1987
1988 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1989 def toggle_confirmation(users) do
1990 Enum.map(users, &toggle_confirmation/1)
1991 end
1992
1993 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1994 mascot
1995 end
1996
1997 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1998 # use instance-default
1999 config = Config.get([:assets, :mascots])
2000 default_mascot = Config.get([:assets, :default_mascot])
2001 mascot = Keyword.get(config, default_mascot)
2002
2003 %{
2004 "id" => "default-mascot",
2005 "url" => mascot[:url],
2006 "preview_url" => mascot[:url],
2007 "pleroma" => %{
2008 "mime_type" => mascot[:mime_type]
2009 }
2010 }
2011 end
2012
2013 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
2014
2015 def ensure_keys_present(%User{} = user) do
2016 with {:ok, pem} <- Keys.generate_rsa_pem() do
2017 user
2018 |> cast(%{keys: pem}, [:keys])
2019 |> validate_required([:keys])
2020 |> update_and_set_cache()
2021 end
2022 end
2023
2024 def get_ap_ids_by_nicknames(nicknames) do
2025 from(u in User,
2026 where: u.nickname in ^nicknames,
2027 select: u.ap_id
2028 )
2029 |> Repo.all()
2030 end
2031
2032 defdelegate search(query, opts \\ []), to: User.Search
2033
2034 defp put_password_hash(
2035 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
2036 ) do
2037 change(changeset, password_hash: Pbkdf2.hash_pwd_salt(password))
2038 end
2039
2040 defp put_password_hash(changeset), do: changeset
2041
2042 def is_internal_user?(%User{nickname: nil}), do: true
2043 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
2044 def is_internal_user?(_), do: false
2045
2046 # A hack because user delete activities have a fake id for whatever reason
2047 # TODO: Get rid of this
2048 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
2049
2050 def get_delivered_users_by_object_id(object_id) do
2051 from(u in User,
2052 inner_join: delivery in assoc(u, :deliveries),
2053 where: delivery.object_id == ^object_id
2054 )
2055 |> Repo.all()
2056 end
2057
2058 def change_email(user, email) do
2059 user
2060 |> cast(%{email: email}, [:email])
2061 |> validate_required([:email])
2062 |> unique_constraint(:email)
2063 |> validate_format(:email, @email_regex)
2064 |> update_and_set_cache()
2065 end
2066
2067 # Internal function; public one is `deactivate/2`
2068 defp set_activation_status(user, deactivated) do
2069 user
2070 |> cast(%{deactivated: deactivated}, [:deactivated])
2071 |> update_and_set_cache()
2072 end
2073
2074 def update_banner(user, banner) do
2075 user
2076 |> cast(%{banner: banner}, [:banner])
2077 |> update_and_set_cache()
2078 end
2079
2080 def update_background(user, background) do
2081 user
2082 |> cast(%{background: background}, [:background])
2083 |> update_and_set_cache()
2084 end
2085
2086 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
2087 %{
2088 admin: is_admin,
2089 moderator: is_moderator
2090 }
2091 end
2092
2093 def validate_fields(changeset, remote? \\ false) do
2094 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2095 limit = Config.get([:instance, limit_name], 0)
2096
2097 changeset
2098 |> validate_length(:fields, max: limit)
2099 |> validate_change(:fields, fn :fields, fields ->
2100 if Enum.all?(fields, &valid_field?/1) do
2101 []
2102 else
2103 [fields: "invalid"]
2104 end
2105 end)
2106 end
2107
2108 defp valid_field?(%{"name" => name, "value" => value}) do
2109 name_limit = Config.get([:instance, :account_field_name_length], 255)
2110 value_limit = Config.get([:instance, :account_field_value_length], 255)
2111
2112 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2113 String.length(value) <= value_limit
2114 end
2115
2116 defp valid_field?(_), do: false
2117
2118 defp truncate_field(%{"name" => name, "value" => value}) do
2119 {name, _chopped} =
2120 String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
2121
2122 {value, _chopped} =
2123 String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
2124
2125 %{"name" => name, "value" => value}
2126 end
2127
2128 def admin_api_update(user, params) do
2129 user
2130 |> cast(params, [
2131 :is_moderator,
2132 :is_admin,
2133 :show_role
2134 ])
2135 |> update_and_set_cache()
2136 end
2137
2138 @doc "Signs user out of all applications"
2139 def global_sign_out(user) do
2140 OAuth.Authorization.delete_user_authorizations(user)
2141 OAuth.Token.delete_user_tokens(user)
2142 end
2143
2144 def mascot_update(user, url) do
2145 user
2146 |> cast(%{mascot: url}, [:mascot])
2147 |> validate_required([:mascot])
2148 |> update_and_set_cache()
2149 end
2150
2151 def mastodon_settings_update(user, settings) do
2152 user
2153 |> cast(%{mastofe_settings: settings}, [:mastofe_settings])
2154 |> validate_required([:mastofe_settings])
2155 |> update_and_set_cache()
2156 end
2157
2158 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2159 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
2160 params =
2161 if need_confirmation? do
2162 %{
2163 confirmation_pending: true,
2164 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2165 }
2166 else
2167 %{
2168 confirmation_pending: false,
2169 confirmation_token: nil
2170 }
2171 end
2172
2173 cast(user, params, [:confirmation_pending, :confirmation_token])
2174 end
2175
2176 @spec approval_changeset(User.t(), keyword()) :: Changeset.t()
2177 def approval_changeset(user, need_approval: need_approval?) do
2178 params = if need_approval?, do: %{approval_pending: true}, else: %{approval_pending: false}
2179 cast(user, params, [:approval_pending])
2180 end
2181
2182 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2183 if id not in user.pinned_activities do
2184 max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
2185 params = %{pinned_activities: user.pinned_activities ++ [id]}
2186
2187 user
2188 |> cast(params, [:pinned_activities])
2189 |> validate_length(:pinned_activities,
2190 max: max_pinned_statuses,
2191 message: "You have already pinned the maximum number of statuses"
2192 )
2193 else
2194 change(user)
2195 end
2196 |> update_and_set_cache()
2197 end
2198
2199 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2200 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2201
2202 user
2203 |> cast(params, [:pinned_activities])
2204 |> update_and_set_cache()
2205 end
2206
2207 def update_email_notifications(user, settings) do
2208 email_notifications =
2209 user.email_notifications
2210 |> Map.merge(settings)
2211 |> Map.take(["digest"])
2212
2213 params = %{email_notifications: email_notifications}
2214 fields = [:email_notifications]
2215
2216 user
2217 |> cast(params, fields)
2218 |> validate_required(fields)
2219 |> update_and_set_cache()
2220 end
2221
2222 defp set_domain_blocks(user, domain_blocks) do
2223 params = %{domain_blocks: domain_blocks}
2224
2225 user
2226 |> cast(params, [:domain_blocks])
2227 |> validate_required([:domain_blocks])
2228 |> update_and_set_cache()
2229 end
2230
2231 def block_domain(user, domain_blocked) do
2232 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2233 end
2234
2235 def unblock_domain(user, domain_blocked) do
2236 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2237 end
2238
2239 @spec add_to_block(User.t(), User.t()) ::
2240 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2241 defp add_to_block(%User{} = user, %User{} = blocked) do
2242 UserRelationship.create_block(user, blocked)
2243 end
2244
2245 @spec add_to_block(User.t(), User.t()) ::
2246 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2247 defp remove_from_block(%User{} = user, %User{} = blocked) do
2248 UserRelationship.delete_block(user, blocked)
2249 end
2250
2251 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2252 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2253 {:ok, user_notification_mute} <-
2254 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2255 {:ok, nil} do
2256 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2257 end
2258 end
2259
2260 defp remove_from_mutes(user, %User{} = muted_user) do
2261 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2262 {:ok, user_notification_mute} <-
2263 UserRelationship.delete_notification_mute(user, muted_user) do
2264 {:ok, [user_mute, user_notification_mute]}
2265 end
2266 end
2267
2268 def set_invisible(user, invisible) do
2269 params = %{invisible: invisible}
2270
2271 user
2272 |> cast(params, [:invisible])
2273 |> validate_required([:invisible])
2274 |> update_and_set_cache()
2275 end
2276
2277 def sanitize_html(%User{} = user) do
2278 sanitize_html(user, nil)
2279 end
2280
2281 # User data that mastodon isn't filtering (treated as plaintext):
2282 # - field name
2283 # - display name
2284 def sanitize_html(%User{} = user, filter) do
2285 fields =
2286 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2287 %{
2288 "name" => name,
2289 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2290 }
2291 end)
2292
2293 user
2294 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2295 |> Map.put(:fields, fields)
2296 end
2297 end