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