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