X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;ds=sidebyside;f=lib%2Fpleroma%2Fuser.ex;h=706aee2ffb8d6aa6eef91a9b3bfe7a509f1d3bf2;hb=5efa5b0c4a08c2bf73d6926ee7c418e31a04195c;hp=e2afc6de8215b3555f41a8f44b56984e388ecac5;hpb=0d2c13a119302d0d217a7cb61c28a01c620b1b61;p=akkoma
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index e2afc6de8..8693c0b80 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors
+# Copyright © 2017-2020 Pleroma Authors
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User do
@@ -12,9 +12,11 @@ defmodule Pleroma.User do
alias Comeonin.Pbkdf2
alias Ecto.Multi
alias Pleroma.Activity
+ alias Pleroma.Config
alias Pleroma.Conversation.Participation
alias Pleroma.Delivery
alias Pleroma.FollowingRelationship
+ alias Pleroma.HTML
alias Pleroma.Keys
alias Pleroma.Notification
alias Pleroma.Object
@@ -35,7 +37,7 @@ defmodule Pleroma.User do
require Logger
@type t :: %__MODULE__{}
-
+ @type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
@@ -127,6 +129,7 @@ defmodule Pleroma.User do
field(:invisible, :boolean, default: false)
field(:allow_following_move, :boolean, default: true)
field(:skip_thread_containment, :boolean, default: false)
+ field(:actor_type, :string, default: "Person")
field(:also_known_as, {:array, :string}, default: [])
embeds_one(
@@ -215,30 +218,50 @@ defmodule Pleroma.User do
end
end
- @doc "Returns if the user should be allowed to authenticate"
- def auth_active?(%User{deactivated: true}), do: false
+ @doc "Returns status account"
+ @spec account_status(User.t()) :: account_status()
+ def account_status(%User{deactivated: true}), do: :deactivated
+ def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
- def auth_active?(%User{confirmation_pending: true}),
- do: !Pleroma.Config.get([:instance, :account_activation_required])
+ def account_status(%User{confirmation_pending: true}) do
+ case Config.get([:instance, :account_activation_required]) do
+ true -> :confirmation_pending
+ _ -> :active
+ end
+ end
- def auth_active?(%User{}), do: true
+ def account_status(%User{}), do: :active
+ @spec visible_for?(User.t(), User.t() | nil) :: boolean()
def visible_for?(user, for_user \\ nil)
def visible_for?(%User{invisible: true}, _), do: false
- def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
+ def visible_for?(%User{id: user_id}, %User{id: user_id}), do: true
+
+ def visible_for?(%User{local: local} = user, nil) do
+ cfg_key =
+ if local,
+ do: :local,
+ else: :remote
+
+ if Config.get([:restrict_unauthenticated, :profiles, cfg_key]),
+ do: false,
+ else: account_status(user) == :active
+ end
def visible_for?(%User{} = user, for_user) do
- auth_active?(user) || superuser?(for_user)
+ account_status(user) == :active || superuser?(for_user)
end
def visible_for?(_, _), do: false
+ @spec superuser?(User.t()) :: boolean()
def superuser?(%User{local: true, is_admin: true}), do: true
def superuser?(%User{local: true, is_moderator: true}), do: true
def superuser?(_), do: false
+ @spec invisible?(User.t()) :: boolean()
def invisible?(%User{invisible: true}), do: true
def invisible?(_), do: false
@@ -346,6 +369,7 @@ defmodule Pleroma.User do
:following_count,
:discoverable,
:invisible,
+ :actor_type,
:also_known_as
]
)
@@ -396,6 +420,7 @@ defmodule Pleroma.User do
:raw_fields,
:pleroma_settings_store,
:discoverable,
+ :actor_type,
:also_known_as
]
)
@@ -438,6 +463,7 @@ defmodule Pleroma.User do
:discoverable,
:hide_followers_count,
:hide_follows_count,
+ :actor_type,
:also_known_as
]
)
@@ -516,7 +542,14 @@ defmodule Pleroma.User do
end
def maybe_validate_required_email(changeset, true), do: changeset
- def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
+
+ def maybe_validate_required_email(changeset, _) do
+ if Pleroma.Config.get([:instance, :account_activation_required]) do
+ validate_required(changeset, [:email])
+ else
+ changeset
+ end
+ end
defp put_ap_id(changeset) do
ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
@@ -633,25 +666,48 @@ defmodule Pleroma.User do
end
end
+ def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
+ {:error, "Not subscribed!"}
+ end
+
def unfollow(%User{} = follower, %User{} = followed) do
- if following?(follower, followed) and follower.ap_id != followed.ap_id do
- FollowingRelationship.unfollow(follower, followed)
+ case get_follow_state(follower, followed) do
+ state when state in ["accept", "pending"] ->
+ FollowingRelationship.unfollow(follower, followed)
+ {:ok, followed} = update_follower_count(followed)
- {:ok, followed} = update_follower_count(followed)
+ {:ok, follower} =
+ follower
+ |> update_following_count()
+ |> set_cache()
- {:ok, follower} =
- follower
- |> update_following_count()
- |> set_cache()
+ {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
- {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
- else
- {:error, "Not subscribed!"}
+ nil ->
+ {:error, "Not subscribed!"}
end
end
defdelegate following?(follower, followed), to: FollowingRelationship
+ def get_follow_state(%User{} = follower, %User{} = following) do
+ following_relationship = FollowingRelationship.get(follower, following)
+
+ case {following_relationship, following.local} do
+ {nil, false} ->
+ case Utils.fetch_latest_follow(follower, following) do
+ %{data: %{"state" => state}} when state in ["pending", "accept"] -> state
+ _ -> nil
+ end
+
+ {%{state: state}, _} ->
+ state
+
+ {nil, _} ->
+ nil
+ end
+ end
+
def locked?(%User{} = user) do
user.locked || false
end
@@ -712,9 +768,18 @@ defmodule Pleroma.User do
Cachex.del(:user_cache, "nickname:#{user.nickname}")
end
+ @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
def get_cached_by_ap_id(ap_id) do
key = "ap_id:#{ap_id}"
- Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
+
+ with {:ok, nil} <- Cachex.get(:user_cache, key),
+ user when not is_nil(user) <- get_by_ap_id(ap_id),
+ {:ok, true} <- Cachex.put(:user_cache, key, user) do
+ user
+ else
+ {:ok, user} -> user
+ nil -> nil
+ end
end
def get_cached_by_id(id) do
@@ -786,10 +851,6 @@ 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
- fetch_initial_posts(user)
- end
-
{:ok, user}
else
_e -> {:error, "not found " <> nickname}
@@ -797,11 +858,6 @@ defmodule Pleroma.User do
end
end
- @doc "Fetch some posts when the user has just been federated with"
- def fetch_initial_posts(user) do
- BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
- end
-
@spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
def get_followers_query(%User{} = user, nil) do
User.Query.build(%{followers: user, deactivated: false})
@@ -816,14 +872,14 @@ defmodule Pleroma.User do
@spec get_followers_query(User.t()) :: Ecto.Query.t()
def get_followers_query(user), do: get_followers_query(user, nil)
- @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
+ @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
def get_followers(user, page \\ nil) do
user
|> get_followers_query(page)
|> Repo.all()
end
- @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
+ @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
def get_external_followers(user, page \\ nil) do
user
|> get_followers_query(page)
@@ -858,6 +914,13 @@ defmodule Pleroma.User do
|> Repo.all()
end
+ def get_friends_ap_ids(user) do
+ user
+ |> get_friends_query(nil)
+ |> select([u], u.ap_id)
+ |> Repo.all()
+ end
+
def get_friends_ids(user, page \\ nil) do
user
|> get_friends_query(page)
@@ -1132,7 +1195,8 @@ defmodule Pleroma.User do
def blocks?(nil, _), do: false
def blocks?(%User{} = user, %User{} = target) do
- blocks_user?(user, target) || blocks_domain?(user, target)
+ blocks_user?(user, target) ||
+ (!User.following?(user, target) && blocks_domain?(user, target))
end
def blocks_user?(%User{} = user, %User{} = target) do
@@ -1259,17 +1323,6 @@ defmodule Pleroma.User do
Repo.delete(user)
end
- @spec perform(atom(), User.t()) :: {:ok, User.t()}
- def perform(:fetch_initial_posts, %User{} = user) do
- pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
-
- # Insert all the posts in reverse order, so they're in the right order on the timeline
- user.source_data["outbox"]
- |> Utils.fetch_ordered_collection(pages)
- |> Enum.reverse()
- |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
- end
-
def perform(:deactivate_async, user, status), do: deactivate(user, status)
@spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
@@ -1291,7 +1344,6 @@ defmodule Pleroma.User do
)
end
- @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
def perform(:follow_import, %User{} = follower, followed_identifiers)
when is_list(followed_identifiers) do
Enum.map(
@@ -1399,18 +1451,7 @@ defmodule Pleroma.User do
if !is_nil(user) and !needs_update?(user) do
{:ok, user}
else
- # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
- should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
-
- resp = fetch_by_ap_id(ap_id)
-
- if should_fetch_initial do
- with {:ok, %User{} = user} <- resp do
- fetch_initial_posts(user)
- end
- end
-
- resp
+ fetch_by_ap_id(ap_id)
end
end
@@ -1418,20 +1459,47 @@ defmodule Pleroma.User do
Creates an internal service actor by URI if missing.
Optionally takes nickname for addressing.
"""
- def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
- with user when is_nil(user) <- get_cached_by_ap_id(uri) do
- {:ok, user} =
- %User{
- invisible: true,
- local: true,
- ap_id: uri,
- nickname: nickname,
- follower_address: uri <> "/followers"
- }
- |> Repo.insert()
+ @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
+ def get_or_create_service_actor_by_ap_id(uri, nickname) do
+ {_, user} =
+ case get_cached_by_ap_id(uri) do
+ nil ->
+ with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
+ Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
+ {:error, nil}
+ end
- user
- end
+ %User{invisible: false} = user ->
+ set_invisible(user)
+
+ user ->
+ {:ok, user}
+ end
+
+ user
+ end
+
+ @spec set_invisible(User.t()) :: {:ok, User.t()}
+ defp set_invisible(user) do
+ user
+ |> change(%{invisible: true})
+ |> update_and_set_cache()
+ end
+
+ @spec create_service_actor(String.t(), String.t()) ::
+ {:ok, User.t()} | {:error, Ecto.Changeset.t()}
+ defp create_service_actor(uri, nickname) do
+ %User{
+ invisible: true,
+ local: true,
+ ap_id: uri,
+ nickname: nickname,
+ follower_address: uri <> "/followers"
+ }
+ |> change
+ |> unique_constraint(:nickname)
+ |> Repo.insert()
+ |> set_cache()
end
# AP style
@@ -1463,7 +1531,7 @@ defmodule Pleroma.User do
data
|> Map.put(:name, blank?(data[:name]) || data[:nickname])
|> remote_user_creation()
- |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
+ |> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
|> set_cache()
end
@@ -1844,6 +1912,12 @@ defmodule Pleroma.User do
|> update_and_set_cache()
end
+ @doc "Signs user out of all applications"
+ def global_sign_out(user) do
+ OAuth.Authorization.delete_user_authorizations(user)
+ OAuth.Token.delete_user_tokens(user)
+ end
+
def mascot_update(user, url) do
user
|> cast(%{mascot: url}, [:mascot])
@@ -1970,4 +2044,27 @@ defmodule Pleroma.User do
|> validate_required([:invisible])
|> update_and_set_cache()
end
+
+ def sanitize_html(%User{} = user) do
+ sanitize_html(user, nil)
+ end
+
+ # User data that mastodon isn't filtering (treated as plaintext):
+ # - field name
+ # - display name
+ def sanitize_html(%User{} = user, filter) do
+ fields =
+ user
+ |> User.fields()
+ |> Enum.map(fn %{"name" => name, "value" => value} ->
+ %{
+ "name" => name,
+ "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
+ }
+ end)
+
+ user
+ |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
+ |> Map.put(:fields, fields)
+ end
end