[#923] Merge remote-tracking branch 'remotes/upstream/develop' into twitter_oauth
[akkoma] / lib / pleroma / user.ex
index 50e7e7ccd64f8a53877dcaf2cc25ae3be1463d4e..7f8b282e07c5d8026aa7922bb8f5a13f763b1fc5 100644 (file)
@@ -8,21 +8,21 @@ defmodule Pleroma.User do
   import Ecto.Changeset
   import Ecto.Query
 
+  alias Comeonin.Pbkdf2
+  alias Pleroma.Activity
+  alias Pleroma.Formatter
+  alias Pleroma.Notification
+  alias Pleroma.Object
   alias Pleroma.Repo
   alias Pleroma.User
-  alias Pleroma.Object
   alias Pleroma.Web
-  alias Pleroma.Activity
-  alias Pleroma.Notification
-  alias Comeonin.Pbkdf2
-  alias Pleroma.Formatter
+  alias Pleroma.Web.ActivityPub.ActivityPub
+  alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
-  alias Pleroma.Web.OStatus
-  alias Pleroma.Web.Websub
   alias Pleroma.Web.OAuth
-  alias Pleroma.Web.ActivityPub.Utils
-  alias Pleroma.Web.ActivityPub.ActivityPub
+  alias Pleroma.Web.OStatus
   alias Pleroma.Web.RelMe
+  alias Pleroma.Web.Websub
 
   require Logger
 
@@ -30,6 +30,7 @@ defmodule Pleroma.User do
 
   @primary_key {:id, Pleroma.FlakeId, autogenerate: true}
 
+  # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
   @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])?)*$/
 
   @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
@@ -40,6 +41,8 @@ defmodule Pleroma.User do
     field(:email, :string)
     field(:name, :string)
     field(:nickname, :string)
+    field(:auth_provider, :string)
+    field(:auth_provider_uid, :string)
     field(:password_hash, :string)
     field(:password, :string, virtual: true)
     field(:password_confirmation, :string, virtual: true)
@@ -206,6 +209,36 @@ defmodule Pleroma.User do
     update_and_set_cache(password_update_changeset(user, data))
   end
 
+  # TODO: FIXME (WIP):
+  def oauth_register_changeset(struct, params \\ %{}) do
+    info_change = User.Info.confirmation_changeset(%User.Info{}, :confirmed)
+
+    changeset =
+      struct
+      |> cast(params, [:email, :nickname, :name, :bio, :auth_provider, :auth_provider_uid])
+      |> validate_required([:auth_provider, :auth_provider_uid])
+      |> unique_constraint(:email)
+      |> unique_constraint(:nickname)
+      |> validate_exclusion(:nickname, Pleroma.Config.get([Pleroma.User, :restricted_nicknames]))
+      |> validate_format(:email, @email_regex)
+      |> validate_length(:bio, max: 1000)
+      |> put_change(:info, info_change)
+
+    if changeset.valid? do
+      nickname = changeset.changes[:nickname]
+      ap_id = (nickname && User.ap_id(%User{nickname: nickname})) || nil
+      followers = User.ap_followers(%User{nickname: ap_id})
+
+      changeset
+      |> put_change(:ap_id, ap_id)
+      |> unique_constraint(:ap_id)
+      |> put_change(:following, [followers])
+      |> put_change(:follower_address, followers)
+    else
+      changeset
+    end
+  end
+
   def register_changeset(struct, params \\ %{}, opts \\ []) do
     confirmation_status =
       if opts[:confirmed] || !Pleroma.Config.get([:instance, :account_activation_required]) do
@@ -285,7 +318,7 @@ defmodule Pleroma.User do
   def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
 
   def needs_update?(%User{local: false} = user) do
-    NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86400
+    NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
   end
 
   def needs_update?(_), do: true
@@ -435,7 +468,8 @@ defmodule Pleroma.User do
     Repo.get_by(User, ap_id: ap_id)
   end
 
-  # This is mostly an SPC migration fix. This guesses the user nickname (by taking the last part of the ap_id and the domain) and tries to get that user
+  # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
+  # of the ap_id and the domain and tries to get that user
   def get_by_guessed_nickname(ap_id) do
     domain = URI.parse(ap_id).host
     name = List.last(String.split(ap_id, "/"))
@@ -504,13 +538,19 @@ defmodule Pleroma.User do
       end
   end
 
+  def get_by_email(email), do: Repo.get_by(User, email: email)
+
   def get_by_nickname_or_email(nickname_or_email) do
-    case user = Repo.get_by(User, nickname: nickname_or_email) do
-      %User{} -> user
-      nil -> Repo.get_by(User, email: nickname_or_email)
-    end
+    get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
   end
 
+  def get_by_auth_provider_uid(auth_provider, auth_provider_uid),
+    do:
+      Repo.get_by(User,
+        auth_provider: to_string(auth_provider),
+        auth_provider_uid: to_string(auth_provider_uid)
+      )
+
   def get_cached_user_info(user) do
     key = "user_info:#{user.id}"
     Cachex.fetch!(:user_cache, key, fn _ -> user_info(user) end)
@@ -532,6 +572,10 @@ defmodule Pleroma.User do
       _e ->
         with [_nick, _domain] <- String.split(nickname, "@"),
              {:ok, user} <- fetch_by_nickname(nickname) do
+          if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
+            {:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
+          end
+
           user
         else
           _e -> nil
@@ -539,6 +583,17 @@ defmodule Pleroma.User do
     end
   end
 
+  @doc "Fetch some posts when the user has just been federated with"
+  def fetch_initial_posts(user) do
+    pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
+
+    Enum.each(
+      # Insert all the posts in reverse order, so they're in the right order on the timeline
+      Enum.reverse(Utils.fetch_ordered_collection(user.info.source_data["outbox"], pages)),
+      &Pleroma.Web.Federator.incoming_ap_doc/1
+    )
+  end
+
   def get_followers_query(%User{id: id, follower_address: follower_address}, nil) do
     from(
       u in User,
@@ -749,13 +804,41 @@ defmodule Pleroma.User do
     Repo.all(query)
   end
 
-  @spec search_for_admin(binary(), %{
+  @spec search_for_admin(%{
+          local: boolean(),
+          page: number(),
+          page_size: number()
+        }) :: {:ok, [Pleroma.User.t()], number()}
+  def search_for_admin(%{query: nil, local: local, page: page, page_size: page_size}) do
+    query =
+      from(u in User, order_by: u.id)
+      |> maybe_local_user_query(local)
+
+    paginated_query =
+      query
+      |> paginate(page, page_size)
+
+    count =
+      query
+      |> Repo.aggregate(:count, :id)
+
+    {:ok, Repo.all(paginated_query), count}
+  end
+
+  @spec search_for_admin(%{
+          query: binary(),
           admin: Pleroma.User.t(),
           local: boolean(),
           page: number(),
           page_size: number()
         }) :: {:ok, [Pleroma.User.t()], number()}
-  def search_for_admin(term, %{admin: admin, local: local, page: page, page_size: page_size}) do
+  def search_for_admin(%{
+        query: term,
+        admin: admin,
+        local: local,
+        page: page,
+        page_size: page_size
+      }) do
     term = String.trim_leading(term, "@")
 
     local_paginated_query =
@@ -774,21 +857,6 @@ defmodule Pleroma.User do
     {:ok, do_search(search_query, admin), count}
   end
 
-  @spec all_for_admin(number(), number()) :: {:ok, [Pleroma.User.t()], number()}
-  def all_for_admin(page, page_size) do
-    query = from(u in User, order_by: u.id)
-
-    paginated_query =
-      query
-      |> paginate(page, page_size)
-
-    count =
-      query
-      |> Repo.aggregate(:count, :id)
-
-    {:ok, Repo.all(paginated_query), count}
-  end
-
   def search(query, resolve \\ false, for_user \\ nil) do
     # Strip the beginning @ off if there is a query
     query = String.trim_leading(query, "@")
@@ -1108,24 +1176,36 @@ defmodule Pleroma.User do
 
   def html_filter_policy(_), do: @default_scrubbers
 
+  def fetch_by_ap_id(ap_id) do
+    ap_try = ActivityPub.make_user_from_ap_id(ap_id)
+
+    case ap_try do
+      {:ok, user} ->
+        user
+
+      _ ->
+        case OStatus.make_user(ap_id) do
+          {:ok, user} -> user
+          _ -> {:error, "Could not fetch by AP id"}
+        end
+    end
+  end
+
   def get_or_fetch_by_ap_id(ap_id) do
     user = get_by_ap_id(ap_id)
 
     if !is_nil(user) and !User.needs_update?(user) do
       user
     else
-      ap_try = ActivityPub.make_user_from_ap_id(ap_id)
+      user = fetch_by_ap_id(ap_id)
 
-      case ap_try do
-        {:ok, user} ->
-          user
-
-        _ ->
-          case OStatus.make_user(ap_id) do
-            {:ok, user} -> user
-            _ -> {:error, "Could not fetch by AP id"}
-          end
+      if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
+        with %User{} = user do
+          {:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
+        end
       end
+
+      user
     end
   end
 
@@ -1300,7 +1380,7 @@ defmodule Pleroma.User do
     |> Enum.map(&String.downcase(&1))
   end
 
-  defp local_nickname_regex() do
+  defp local_nickname_regex do
     if Pleroma.Config.get([:instance, :extended_nickname_format]) do
       @extended_local_nickname_regex
     else
@@ -1343,4 +1423,8 @@ defmodule Pleroma.User do
       offset: ^((page - 1) * page_size)
     )
   end
+
+  def showing_reblogs?(%User{} = user, %User{} = target) do
+    target.ap_id not in user.info.muted_reblogs
+  end
 end