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