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