Merge branch '1427-oauth-admin-scopes' 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_ids(user, page \\ nil) do
866 user
867 |> get_friends_query(page)
868 |> select([u], u.id)
869 |> Repo.all()
870 end
871
872 defdelegate get_follow_requests(user), to: FollowingRelationship
873
874 def increase_note_count(%User{} = user) do
875 User
876 |> where(id: ^user.id)
877 |> update([u], inc: [note_count: 1])
878 |> select([u], u)
879 |> Repo.update_all([])
880 |> case do
881 {1, [user]} -> set_cache(user)
882 _ -> {:error, user}
883 end
884 end
885
886 def decrease_note_count(%User{} = user) do
887 User
888 |> where(id: ^user.id)
889 |> update([u],
890 set: [
891 note_count: fragment("greatest(0, note_count - 1)")
892 ]
893 )
894 |> select([u], u)
895 |> Repo.update_all([])
896 |> case do
897 {1, [user]} -> set_cache(user)
898 _ -> {:error, user}
899 end
900 end
901
902 def update_note_count(%User{} = user, note_count \\ nil) do
903 note_count =
904 note_count ||
905 from(
906 a in Object,
907 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
908 select: count(a.id)
909 )
910 |> Repo.one()
911
912 user
913 |> cast(%{note_count: note_count}, [:note_count])
914 |> update_and_set_cache()
915 end
916
917 @spec maybe_fetch_follow_information(User.t()) :: User.t()
918 def maybe_fetch_follow_information(user) do
919 with {:ok, user} <- fetch_follow_information(user) do
920 user
921 else
922 e ->
923 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
924
925 user
926 end
927 end
928
929 def fetch_follow_information(user) do
930 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
931 user
932 |> follow_information_changeset(info)
933 |> update_and_set_cache()
934 end
935 end
936
937 defp follow_information_changeset(user, params) do
938 user
939 |> cast(params, [
940 :hide_followers,
941 :hide_follows,
942 :follower_count,
943 :following_count,
944 :hide_followers_count,
945 :hide_follows_count
946 ])
947 end
948
949 def update_follower_count(%User{} = user) do
950 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
951 follower_count_query =
952 User.Query.build(%{followers: user, deactivated: false})
953 |> select([u], %{count: count(u.id)})
954
955 User
956 |> where(id: ^user.id)
957 |> join(:inner, [u], s in subquery(follower_count_query))
958 |> update([u, s],
959 set: [follower_count: s.count]
960 )
961 |> select([u], u)
962 |> Repo.update_all([])
963 |> case do
964 {1, [user]} -> set_cache(user)
965 _ -> {:error, user}
966 end
967 else
968 {:ok, maybe_fetch_follow_information(user)}
969 end
970 end
971
972 @spec update_following_count(User.t()) :: User.t()
973 def update_following_count(%User{local: false} = user) do
974 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
975 maybe_fetch_follow_information(user)
976 else
977 user
978 end
979 end
980
981 def update_following_count(%User{local: true} = user) do
982 following_count = FollowingRelationship.following_count(user)
983
984 user
985 |> follow_information_changeset(%{following_count: following_count})
986 |> Repo.update!()
987 end
988
989 def set_unread_conversation_count(%User{local: true} = user) do
990 unread_query = Participation.unread_conversation_count_for_user(user)
991
992 User
993 |> join(:inner, [u], p in subquery(unread_query))
994 |> update([u, p],
995 set: [unread_conversation_count: p.count]
996 )
997 |> where([u], u.id == ^user.id)
998 |> select([u], u)
999 |> Repo.update_all([])
1000 |> case do
1001 {1, [user]} -> set_cache(user)
1002 _ -> {:error, user}
1003 end
1004 end
1005
1006 def set_unread_conversation_count(user), do: {:ok, user}
1007
1008 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1009 unread_query =
1010 Participation.unread_conversation_count_for_user(user)
1011 |> where([p], p.conversation_id == ^conversation.id)
1012
1013 User
1014 |> join(:inner, [u], p in subquery(unread_query))
1015 |> update([u, p],
1016 inc: [unread_conversation_count: 1]
1017 )
1018 |> where([u], u.id == ^user.id)
1019 |> where([u, p], p.count == 0)
1020 |> select([u], u)
1021 |> Repo.update_all([])
1022 |> case do
1023 {1, [user]} -> set_cache(user)
1024 _ -> {:error, user}
1025 end
1026 end
1027
1028 def increment_unread_conversation_count(_, user), do: {:ok, user}
1029
1030 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1031 def get_users_from_set(ap_ids, local_only \\ true) do
1032 criteria = %{ap_id: ap_ids, deactivated: false}
1033 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1034
1035 User.Query.build(criteria)
1036 |> Repo.all()
1037 end
1038
1039 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1040 def get_recipients_from_activity(%Activity{recipients: to}) do
1041 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1042 |> Repo.all()
1043 end
1044
1045 @spec mute(User.t(), User.t(), boolean()) ::
1046 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1047 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1048 add_to_mutes(muter, mutee, notifications?)
1049 end
1050
1051 def unmute(%User{} = muter, %User{} = mutee) do
1052 remove_from_mutes(muter, mutee)
1053 end
1054
1055 def subscribe(%User{} = subscriber, %User{} = target) do
1056 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1057
1058 if blocks?(target, subscriber) and deny_follow_blocked do
1059 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1060 else
1061 # Note: the relationship is inverse: subscriber acts as relationship target
1062 UserRelationship.create_inverse_subscription(target, subscriber)
1063 end
1064 end
1065
1066 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1067 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1068 subscribe(subscriber, subscribee)
1069 end
1070 end
1071
1072 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1073 # Note: the relationship is inverse: subscriber acts as relationship target
1074 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1075 end
1076
1077 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1078 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1079 unsubscribe(unsubscriber, user)
1080 end
1081 end
1082
1083 def block(%User{} = blocker, %User{} = blocked) do
1084 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1085 blocker =
1086 if following?(blocker, blocked) do
1087 {:ok, blocker, _} = unfollow(blocker, blocked)
1088 blocker
1089 else
1090 blocker
1091 end
1092
1093 # clear any requested follows as well
1094 blocked =
1095 case CommonAPI.reject_follow_request(blocked, blocker) do
1096 {:ok, %User{} = updated_blocked} -> updated_blocked
1097 nil -> blocked
1098 end
1099
1100 unsubscribe(blocked, blocker)
1101
1102 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1103
1104 {:ok, blocker} = update_follower_count(blocker)
1105 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1106 add_to_block(blocker, blocked)
1107 end
1108
1109 # helper to handle the block given only an actor's AP id
1110 def block(%User{} = blocker, %{ap_id: ap_id}) do
1111 block(blocker, get_cached_by_ap_id(ap_id))
1112 end
1113
1114 def unblock(%User{} = blocker, %User{} = blocked) do
1115 remove_from_block(blocker, blocked)
1116 end
1117
1118 # helper to handle the block given only an actor's AP id
1119 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1120 unblock(blocker, get_cached_by_ap_id(ap_id))
1121 end
1122
1123 def mutes?(nil, _), do: false
1124 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1125
1126 def mutes_user?(%User{} = user, %User{} = target) do
1127 UserRelationship.mute_exists?(user, target)
1128 end
1129
1130 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1131 def muted_notifications?(nil, _), do: false
1132
1133 def muted_notifications?(%User{} = user, %User{} = target),
1134 do: UserRelationship.notification_mute_exists?(user, target)
1135
1136 def blocks?(nil, _), do: false
1137
1138 def blocks?(%User{} = user, %User{} = target) do
1139 blocks_user?(user, target) || blocks_domain?(user, target)
1140 end
1141
1142 def blocks_user?(%User{} = user, %User{} = target) do
1143 UserRelationship.block_exists?(user, target)
1144 end
1145
1146 def blocks_user?(_, _), do: false
1147
1148 def blocks_domain?(%User{} = user, %User{} = target) do
1149 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1150 %{host: host} = URI.parse(target.ap_id)
1151 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1152 end
1153
1154 def blocks_domain?(_, _), do: false
1155
1156 def subscribed_to?(%User{} = user, %User{} = target) do
1157 # Note: the relationship is inverse: subscriber acts as relationship target
1158 UserRelationship.inverse_subscription_exists?(target, user)
1159 end
1160
1161 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1162 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1163 subscribed_to?(user, target)
1164 end
1165 end
1166
1167 @doc """
1168 Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
1169 E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1170 """
1171 @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1172 def outgoing_relations_ap_ids(_, []), do: %{}
1173
1174 def outgoing_relations_ap_ids(%User{} = user, relationship_types)
1175 when is_list(relationship_types) do
1176 db_result =
1177 user
1178 |> assoc(:outgoing_relationships)
1179 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1180 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1181 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1182 |> group_by([user_rel, u], user_rel.relationship_type)
1183 |> Repo.all()
1184 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1185
1186 Enum.into(
1187 relationship_types,
1188 %{},
1189 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1190 )
1191 end
1192
1193 def deactivate_async(user, status \\ true) do
1194 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1195 end
1196
1197 def deactivate(user, status \\ true)
1198
1199 def deactivate(users, status) when is_list(users) do
1200 Repo.transaction(fn ->
1201 for user <- users, do: deactivate(user, status)
1202 end)
1203 end
1204
1205 def deactivate(%User{} = user, status) do
1206 with {:ok, user} <- set_activation_status(user, status) do
1207 user
1208 |> get_followers()
1209 |> Enum.filter(& &1.local)
1210 |> Enum.each(fn follower ->
1211 follower |> update_following_count() |> set_cache()
1212 end)
1213
1214 # Only update local user counts, remote will be update during the next pull.
1215 user
1216 |> get_friends()
1217 |> Enum.filter(& &1.local)
1218 |> Enum.each(&update_follower_count/1)
1219
1220 {:ok, user}
1221 end
1222 end
1223
1224 def update_notification_settings(%User{} = user, settings) do
1225 user
1226 |> cast(%{notification_settings: settings}, [])
1227 |> cast_embed(:notification_settings)
1228 |> validate_required([:notification_settings])
1229 |> update_and_set_cache()
1230 end
1231
1232 def delete(users) when is_list(users) do
1233 for user <- users, do: delete(user)
1234 end
1235
1236 def delete(%User{} = user) do
1237 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1238 end
1239
1240 def perform(:force_password_reset, user), do: force_password_reset(user)
1241
1242 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1243 def perform(:delete, %User{} = user) do
1244 {:ok, _user} = ActivityPub.delete(user)
1245
1246 # Remove all relationships
1247 user
1248 |> get_followers()
1249 |> Enum.each(fn follower ->
1250 ActivityPub.unfollow(follower, user)
1251 unfollow(follower, user)
1252 end)
1253
1254 user
1255 |> get_friends()
1256 |> Enum.each(fn followed ->
1257 ActivityPub.unfollow(user, followed)
1258 unfollow(user, followed)
1259 end)
1260
1261 delete_user_activities(user)
1262 invalidate_cache(user)
1263 Repo.delete(user)
1264 end
1265
1266 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1267 def perform(:fetch_initial_posts, %User{} = user) do
1268 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1269
1270 # Insert all the posts in reverse order, so they're in the right order on the timeline
1271 user.source_data["outbox"]
1272 |> Utils.fetch_ordered_collection(pages)
1273 |> Enum.reverse()
1274 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1275 end
1276
1277 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1278
1279 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1280 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1281 when is_list(blocked_identifiers) do
1282 Enum.map(
1283 blocked_identifiers,
1284 fn blocked_identifier ->
1285 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1286 {:ok, _user_block} <- block(blocker, blocked),
1287 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1288 blocked
1289 else
1290 err ->
1291 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1292 err
1293 end
1294 end
1295 )
1296 end
1297
1298 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1299 def perform(:follow_import, %User{} = follower, followed_identifiers)
1300 when is_list(followed_identifiers) do
1301 Enum.map(
1302 followed_identifiers,
1303 fn followed_identifier ->
1304 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1305 {:ok, follower} <- maybe_direct_follow(follower, followed),
1306 {:ok, _} <- ActivityPub.follow(follower, followed) do
1307 followed
1308 else
1309 err ->
1310 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1311 err
1312 end
1313 end
1314 )
1315 end
1316
1317 @spec external_users_query() :: Ecto.Query.t()
1318 def external_users_query do
1319 User.Query.build(%{
1320 external: true,
1321 active: true,
1322 order_by: :id
1323 })
1324 end
1325
1326 @spec external_users(keyword()) :: [User.t()]
1327 def external_users(opts \\ []) do
1328 query =
1329 external_users_query()
1330 |> select([u], struct(u, [:id, :ap_id]))
1331
1332 query =
1333 if opts[:max_id],
1334 do: where(query, [u], u.id > ^opts[:max_id]),
1335 else: query
1336
1337 query =
1338 if opts[:limit],
1339 do: limit(query, ^opts[:limit]),
1340 else: query
1341
1342 Repo.all(query)
1343 end
1344
1345 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1346 BackgroundWorker.enqueue("blocks_import", %{
1347 "blocker_id" => blocker.id,
1348 "blocked_identifiers" => blocked_identifiers
1349 })
1350 end
1351
1352 def follow_import(%User{} = follower, followed_identifiers)
1353 when is_list(followed_identifiers) do
1354 BackgroundWorker.enqueue("follow_import", %{
1355 "follower_id" => follower.id,
1356 "followed_identifiers" => followed_identifiers
1357 })
1358 end
1359
1360 def delete_user_activities(%User{ap_id: ap_id}) do
1361 ap_id
1362 |> Activity.Queries.by_actor()
1363 |> RepoStreamer.chunk_stream(50)
1364 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1365 |> Stream.run()
1366 end
1367
1368 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1369 activity
1370 |> Object.normalize()
1371 |> ActivityPub.delete()
1372 end
1373
1374 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1375 object = Object.normalize(activity)
1376
1377 activity.actor
1378 |> get_cached_by_ap_id()
1379 |> ActivityPub.unlike(object)
1380 end
1381
1382 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1383 object = Object.normalize(activity)
1384
1385 activity.actor
1386 |> get_cached_by_ap_id()
1387 |> ActivityPub.unannounce(object)
1388 end
1389
1390 defp delete_activity(_activity), do: "Doing nothing"
1391
1392 def html_filter_policy(%User{no_rich_text: true}) do
1393 Pleroma.HTML.Scrubber.TwitterText
1394 end
1395
1396 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1397
1398 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1399
1400 def get_or_fetch_by_ap_id(ap_id) do
1401 user = get_cached_by_ap_id(ap_id)
1402
1403 if !is_nil(user) and !needs_update?(user) do
1404 {:ok, user}
1405 else
1406 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1407 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1408
1409 resp = fetch_by_ap_id(ap_id)
1410
1411 if should_fetch_initial do
1412 with {:ok, %User{} = user} <- resp do
1413 fetch_initial_posts(user)
1414 end
1415 end
1416
1417 resp
1418 end
1419 end
1420
1421 @doc """
1422 Creates an internal service actor by URI if missing.
1423 Optionally takes nickname for addressing.
1424 """
1425 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1426 with user when is_nil(user) <- get_cached_by_ap_id(uri) do
1427 {:ok, user} =
1428 %User{
1429 invisible: true,
1430 local: true,
1431 ap_id: uri,
1432 nickname: nickname,
1433 follower_address: uri <> "/followers"
1434 }
1435 |> Repo.insert()
1436
1437 user
1438 end
1439 end
1440
1441 # AP style
1442 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1443 key =
1444 public_key_pem
1445 |> :public_key.pem_decode()
1446 |> hd()
1447 |> :public_key.pem_entry_decode()
1448
1449 {:ok, key}
1450 end
1451
1452 def public_key(_), do: {:error, "not found key"}
1453
1454 def get_public_key_for_ap_id(ap_id) do
1455 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1456 {:ok, public_key} <- public_key(user) do
1457 {:ok, public_key}
1458 else
1459 _ -> :error
1460 end
1461 end
1462
1463 defp blank?(""), do: nil
1464 defp blank?(n), do: n
1465
1466 def insert_or_update_user(data) do
1467 data
1468 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1469 |> remote_user_creation()
1470 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1471 |> set_cache()
1472 end
1473
1474 def ap_enabled?(%User{local: true}), do: true
1475 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1476 def ap_enabled?(_), do: false
1477
1478 @doc "Gets or fetch a user by uri or nickname."
1479 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1480 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1481 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1482
1483 # wait a period of time and return newest version of the User structs
1484 # this is because we have synchronous follow APIs and need to simulate them
1485 # with an async handshake
1486 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1487 with %User{} = a <- get_cached_by_id(a.id),
1488 %User{} = b <- get_cached_by_id(b.id) do
1489 {:ok, a, b}
1490 else
1491 nil -> :error
1492 end
1493 end
1494
1495 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1496 with :ok <- :timer.sleep(timeout),
1497 %User{} = a <- get_cached_by_id(a.id),
1498 %User{} = b <- get_cached_by_id(b.id) do
1499 {:ok, a, b}
1500 else
1501 nil -> :error
1502 end
1503 end
1504
1505 def parse_bio(bio) when is_binary(bio) and bio != "" do
1506 bio
1507 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1508 |> elem(0)
1509 end
1510
1511 def parse_bio(_), do: ""
1512
1513 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1514 # TODO: get profile URLs other than user.ap_id
1515 profile_urls = [user.ap_id]
1516
1517 bio
1518 |> CommonUtils.format_input("text/plain",
1519 mentions_format: :full,
1520 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1521 )
1522 |> elem(0)
1523 end
1524
1525 def parse_bio(_, _), do: ""
1526
1527 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1528 Repo.transaction(fn ->
1529 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1530 end)
1531 end
1532
1533 def tag(nickname, tags) when is_binary(nickname),
1534 do: tag(get_by_nickname(nickname), tags)
1535
1536 def tag(%User{} = user, tags),
1537 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1538
1539 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1540 Repo.transaction(fn ->
1541 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1542 end)
1543 end
1544
1545 def untag(nickname, tags) when is_binary(nickname),
1546 do: untag(get_by_nickname(nickname), tags)
1547
1548 def untag(%User{} = user, tags),
1549 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1550
1551 defp update_tags(%User{} = user, new_tags) do
1552 {:ok, updated_user} =
1553 user
1554 |> change(%{tags: new_tags})
1555 |> update_and_set_cache()
1556
1557 updated_user
1558 end
1559
1560 defp normalize_tags(tags) do
1561 [tags]
1562 |> List.flatten()
1563 |> Enum.map(&String.downcase/1)
1564 end
1565
1566 defp local_nickname_regex do
1567 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1568 @extended_local_nickname_regex
1569 else
1570 @strict_local_nickname_regex
1571 end
1572 end
1573
1574 def local_nickname(nickname_or_mention) do
1575 nickname_or_mention
1576 |> full_nickname()
1577 |> String.split("@")
1578 |> hd()
1579 end
1580
1581 def full_nickname(nickname_or_mention),
1582 do: String.trim_leading(nickname_or_mention, "@")
1583
1584 def error_user(ap_id) do
1585 %User{
1586 name: ap_id,
1587 ap_id: ap_id,
1588 nickname: "erroruser@example.com",
1589 inserted_at: NaiveDateTime.utc_now()
1590 }
1591 end
1592
1593 @spec all_superusers() :: [User.t()]
1594 def all_superusers do
1595 User.Query.build(%{super_users: true, local: true, deactivated: false})
1596 |> Repo.all()
1597 end
1598
1599 def showing_reblogs?(%User{} = user, %User{} = target) do
1600 not UserRelationship.reblog_mute_exists?(user, target)
1601 end
1602
1603 @doc """
1604 The function returns a query to get users with no activity for given interval of days.
1605 Inactive users are those who didn't read any notification, or had any activity where
1606 the user is the activity's actor, during `inactivity_threshold` days.
1607 Deactivated users will not appear in this list.
1608
1609 ## Examples
1610
1611 iex> Pleroma.User.list_inactive_users()
1612 %Ecto.Query{}
1613 """
1614 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1615 def list_inactive_users_query(inactivity_threshold \\ 7) do
1616 negative_inactivity_threshold = -inactivity_threshold
1617 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1618 # Subqueries are not supported in `where` clauses, join gets too complicated.
1619 has_read_notifications =
1620 from(n in Pleroma.Notification,
1621 where: n.seen == true,
1622 group_by: n.id,
1623 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1624 select: n.user_id
1625 )
1626 |> Pleroma.Repo.all()
1627
1628 from(u in Pleroma.User,
1629 left_join: a in Pleroma.Activity,
1630 on: u.ap_id == a.actor,
1631 where: not is_nil(u.nickname),
1632 where: u.deactivated != ^true,
1633 where: u.id not in ^has_read_notifications,
1634 group_by: u.id,
1635 having:
1636 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1637 is_nil(max(a.inserted_at))
1638 )
1639 end
1640
1641 @doc """
1642 Enable or disable email notifications for user
1643
1644 ## Examples
1645
1646 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1647 Pleroma.User{email_notifications: %{"digest" => true}}
1648
1649 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1650 Pleroma.User{email_notifications: %{"digest" => false}}
1651 """
1652 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1653 {:ok, t()} | {:error, Ecto.Changeset.t()}
1654 def switch_email_notifications(user, type, status) do
1655 User.update_email_notifications(user, %{type => status})
1656 end
1657
1658 @doc """
1659 Set `last_digest_emailed_at` value for the user to current time
1660 """
1661 @spec touch_last_digest_emailed_at(t()) :: t()
1662 def touch_last_digest_emailed_at(user) do
1663 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1664
1665 {:ok, updated_user} =
1666 user
1667 |> change(%{last_digest_emailed_at: now})
1668 |> update_and_set_cache()
1669
1670 updated_user
1671 end
1672
1673 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1674 def toggle_confirmation(%User{} = user) do
1675 user
1676 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1677 |> update_and_set_cache()
1678 end
1679
1680 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1681 def toggle_confirmation(users) do
1682 Enum.map(users, &toggle_confirmation/1)
1683 end
1684
1685 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1686 mascot
1687 end
1688
1689 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1690 # use instance-default
1691 config = Pleroma.Config.get([:assets, :mascots])
1692 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1693 mascot = Keyword.get(config, default_mascot)
1694
1695 %{
1696 "id" => "default-mascot",
1697 "url" => mascot[:url],
1698 "preview_url" => mascot[:url],
1699 "pleroma" => %{
1700 "mime_type" => mascot[:mime_type]
1701 }
1702 }
1703 end
1704
1705 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1706
1707 def ensure_keys_present(%User{} = user) do
1708 with {:ok, pem} <- Keys.generate_rsa_pem() do
1709 user
1710 |> cast(%{keys: pem}, [:keys])
1711 |> validate_required([:keys])
1712 |> update_and_set_cache()
1713 end
1714 end
1715
1716 def get_ap_ids_by_nicknames(nicknames) do
1717 from(u in User,
1718 where: u.nickname in ^nicknames,
1719 select: u.ap_id
1720 )
1721 |> Repo.all()
1722 end
1723
1724 defdelegate search(query, opts \\ []), to: User.Search
1725
1726 defp put_password_hash(
1727 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1728 ) do
1729 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1730 end
1731
1732 defp put_password_hash(changeset), do: changeset
1733
1734 def is_internal_user?(%User{nickname: nil}), do: true
1735 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1736 def is_internal_user?(_), do: false
1737
1738 # A hack because user delete activities have a fake id for whatever reason
1739 # TODO: Get rid of this
1740 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1741
1742 def get_delivered_users_by_object_id(object_id) do
1743 from(u in User,
1744 inner_join: delivery in assoc(u, :deliveries),
1745 where: delivery.object_id == ^object_id
1746 )
1747 |> Repo.all()
1748 end
1749
1750 def change_email(user, email) do
1751 user
1752 |> cast(%{email: email}, [:email])
1753 |> validate_required([:email])
1754 |> unique_constraint(:email)
1755 |> validate_format(:email, @email_regex)
1756 |> update_and_set_cache()
1757 end
1758
1759 # Internal function; public one is `deactivate/2`
1760 defp set_activation_status(user, deactivated) do
1761 user
1762 |> cast(%{deactivated: deactivated}, [:deactivated])
1763 |> update_and_set_cache()
1764 end
1765
1766 def update_banner(user, banner) do
1767 user
1768 |> cast(%{banner: banner}, [:banner])
1769 |> update_and_set_cache()
1770 end
1771
1772 def update_background(user, background) do
1773 user
1774 |> cast(%{background: background}, [:background])
1775 |> update_and_set_cache()
1776 end
1777
1778 def update_source_data(user, source_data) do
1779 user
1780 |> cast(%{source_data: source_data}, [:source_data])
1781 |> update_and_set_cache()
1782 end
1783
1784 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1785 %{
1786 admin: is_admin,
1787 moderator: is_moderator
1788 }
1789 end
1790
1791 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1792 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1793 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1794 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1795
1796 attachment
1797 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1798 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1799 |> Enum.take(limit)
1800 end
1801
1802 def fields(%{fields: nil}), do: []
1803
1804 def fields(%{fields: fields}), do: fields
1805
1806 def validate_fields(changeset, remote? \\ false) do
1807 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1808 limit = Pleroma.Config.get([:instance, limit_name], 0)
1809
1810 changeset
1811 |> validate_length(:fields, max: limit)
1812 |> validate_change(:fields, fn :fields, fields ->
1813 if Enum.all?(fields, &valid_field?/1) do
1814 []
1815 else
1816 [fields: "invalid"]
1817 end
1818 end)
1819 end
1820
1821 defp valid_field?(%{"name" => name, "value" => value}) do
1822 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1823 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1824
1825 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1826 String.length(value) <= value_limit
1827 end
1828
1829 defp valid_field?(_), do: false
1830
1831 defp truncate_field(%{"name" => name, "value" => value}) do
1832 {name, _chopped} =
1833 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1834
1835 {value, _chopped} =
1836 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1837
1838 %{"name" => name, "value" => value}
1839 end
1840
1841 def admin_api_update(user, params) do
1842 changeset =
1843 cast(user, params, [
1844 :is_moderator,
1845 :is_admin,
1846 :show_role
1847 ])
1848
1849 with {:ok, updated_user} <- update_and_set_cache(changeset) do
1850 if user.is_admin && !updated_user.is_admin do
1851 # Tokens & authorizations containing any admin scopes must be revoked (revoking all).
1852 # This is an extra safety measure (tokens' admin scopes won't be accepted for non-admins).
1853 global_sign_out(user)
1854 end
1855
1856 {:ok, updated_user}
1857 end
1858 end
1859
1860 @doc "Signs user out of all applications"
1861 def global_sign_out(user) do
1862 OAuth.Authorization.delete_user_authorizations(user)
1863 OAuth.Token.delete_user_tokens(user)
1864 end
1865
1866 def mascot_update(user, url) do
1867 user
1868 |> cast(%{mascot: url}, [:mascot])
1869 |> validate_required([:mascot])
1870 |> update_and_set_cache()
1871 end
1872
1873 def mastodon_settings_update(user, settings) do
1874 user
1875 |> cast(%{settings: settings}, [:settings])
1876 |> validate_required([:settings])
1877 |> update_and_set_cache()
1878 end
1879
1880 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1881 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1882 params =
1883 if need_confirmation? do
1884 %{
1885 confirmation_pending: true,
1886 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1887 }
1888 else
1889 %{
1890 confirmation_pending: false,
1891 confirmation_token: nil
1892 }
1893 end
1894
1895 cast(user, params, [:confirmation_pending, :confirmation_token])
1896 end
1897
1898 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1899 if id not in user.pinned_activities do
1900 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1901 params = %{pinned_activities: user.pinned_activities ++ [id]}
1902
1903 user
1904 |> cast(params, [:pinned_activities])
1905 |> validate_length(:pinned_activities,
1906 max: max_pinned_statuses,
1907 message: "You have already pinned the maximum number of statuses"
1908 )
1909 else
1910 change(user)
1911 end
1912 |> update_and_set_cache()
1913 end
1914
1915 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1916 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1917
1918 user
1919 |> cast(params, [:pinned_activities])
1920 |> update_and_set_cache()
1921 end
1922
1923 def update_email_notifications(user, settings) do
1924 email_notifications =
1925 user.email_notifications
1926 |> Map.merge(settings)
1927 |> Map.take(["digest"])
1928
1929 params = %{email_notifications: email_notifications}
1930 fields = [:email_notifications]
1931
1932 user
1933 |> cast(params, fields)
1934 |> validate_required(fields)
1935 |> update_and_set_cache()
1936 end
1937
1938 defp set_domain_blocks(user, domain_blocks) do
1939 params = %{domain_blocks: domain_blocks}
1940
1941 user
1942 |> cast(params, [:domain_blocks])
1943 |> validate_required([:domain_blocks])
1944 |> update_and_set_cache()
1945 end
1946
1947 def block_domain(user, domain_blocked) do
1948 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1949 end
1950
1951 def unblock_domain(user, domain_blocked) do
1952 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1953 end
1954
1955 @spec add_to_block(User.t(), User.t()) ::
1956 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
1957 defp add_to_block(%User{} = user, %User{} = blocked) do
1958 UserRelationship.create_block(user, blocked)
1959 end
1960
1961 @spec add_to_block(User.t(), User.t()) ::
1962 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
1963 defp remove_from_block(%User{} = user, %User{} = blocked) do
1964 UserRelationship.delete_block(user, blocked)
1965 end
1966
1967 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
1968 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
1969 {:ok, user_notification_mute} <-
1970 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
1971 {:ok, nil} do
1972 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
1973 end
1974 end
1975
1976 defp remove_from_mutes(user, %User{} = muted_user) do
1977 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
1978 {:ok, user_notification_mute} <-
1979 UserRelationship.delete_notification_mute(user, muted_user) do
1980 {:ok, [user_mute, user_notification_mute]}
1981 end
1982 end
1983
1984 def set_invisible(user, invisible) do
1985 params = %{invisible: invisible}
1986
1987 user
1988 |> cast(params, [:invisible])
1989 |> validate_required([:invisible])
1990 |> update_and_set_cache()
1991 end
1992 end