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