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