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