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