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