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