X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fuser.ex;h=1edded4151fe0e4a16a9635acec473c04c77c6a8;hb=ffea81b84f73c75853518f7e0c28b8429f752a36;hp=a290db04adb91135c321c6ffec2a70e17da9c192;hpb=94d8f1ab305b513931b81d7a4b3e41e30b688a16;p=akkoma diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index a290db04a..1edded415 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1,8 +1,11 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.User do use Ecto.Schema import Ecto.{Changeset, Query} - alias Ecto.Multi alias Pleroma.{Repo, User, Object, Web, Activity, Notification} alias Comeonin.Pbkdf2 alias Pleroma.Formatter @@ -10,6 +13,15 @@ defmodule Pleroma.User do alias Pleroma.Web.{OStatus, Websub, OAuth} alias Pleroma.Web.ActivityPub.{Utils, ActivityPub} + require Logger + + @type t :: %__MODULE__{} + + @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]+$/ + @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/ + schema "users" do field(:bio, :string) field(:email, :string) @@ -32,6 +44,21 @@ defmodule Pleroma.User do timestamps() end + def auth_active?(%User{} = user) do + (user.info && !user.info.confirmation_pending) || + !Pleroma.Config.get([:instance, :account_activation_required]) + end + + def remote_or_auth_active?(%User{} = user), do: !user.local || auth_active?(user) + + def visible_for?(%User{} = user, for_user \\ nil) do + User.remote_or_auth_active?(user) || (for_user && for_user.id == user.id) || + User.superuser?(for_user) + end + + def superuser?(nil), do: false + def superuser?(%User{} = user), do: user.info && User.Info.superuser?(user.info) + def avatar_url(user) do case user.avatar do %{"url" => [%{"href" => href} | _]} -> href @@ -72,11 +99,11 @@ defmodule Pleroma.User do note_count: user.info.note_count, follower_count: user.info.follower_count, locked: user.info.locked, + confirmation_pending: user.info.confirmation_pending, default_scope: user.info.default_scope } end - @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])?)*$/ def remote_user_creation(params) do params = params @@ -116,7 +143,7 @@ defmodule Pleroma.User do struct |> cast(params, [:bio, :name, :avatar]) |> unique_constraint(:nickname) - |> validate_format(:nickname, ~r/^[a-zA-Z\d]+$/) + |> validate_format(:nickname, local_nickname_regex()) |> validate_length(:bio, max: 5000) |> validate_length(:name, min: 1, max: 100) end @@ -133,7 +160,7 @@ defmodule Pleroma.User do struct |> cast(params, [:bio, :name, :follower_address, :avatar, :last_refreshed_at]) |> unique_constraint(:nickname) - |> validate_format(:nickname, ~r/^[a-zA-Z\d]+$/) + |> validate_format(:nickname, local_nickname_regex()) |> validate_length(:bio, max: 5000) |> validate_length(:name, max: 100) |> put_embed(:info, info_cng) @@ -163,7 +190,16 @@ defmodule Pleroma.User do update_and_set_cache(password_update_changeset(user, data)) end - def register_changeset(struct, params \\ %{}) do + def register_changeset(struct, params \\ %{}, opts \\ []) do + confirmation_status = + if opts[:confirmed] || !Pleroma.Config.get([:instance, :account_activation_required]) do + :confirmed + else + :unconfirmed + end + + info_change = User.Info.confirmation_changeset(%User.Info{}, confirmation_status) + changeset = struct |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation]) @@ -171,11 +207,12 @@ defmodule Pleroma.User do |> validate_confirmation(:password) |> unique_constraint(:email) |> unique_constraint(:nickname) - |> validate_format(:nickname, ~r/^[a-zA-Z\d]+$/) + |> validate_exclusion(:nickname, Pleroma.Config.get([Pleroma.User, :restricted_nicknames])) + |> validate_format(:nickname, local_nickname_regex()) |> validate_format(:email, @email_regex) |> validate_length(:bio, max: 1000) |> validate_length(:name, min: 1, max: 100) - |> put_change(:info, %Pleroma.User.Info{}) + |> put_change(:info, info_change) if changeset.valid? do hashed = Pbkdf2.hashpwsalt(changeset.changes[:password]) @@ -192,6 +229,25 @@ defmodule Pleroma.User do end end + @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)" + def register(%Ecto.Changeset{} = changeset) do + with {:ok, user} <- Repo.insert(changeset), + {:ok, _} = try_send_confirmation_email(user) do + {:ok, user} + end + end + + def try_send_confirmation_email(%User{} = user) do + if user.info.confirmation_pending && + Pleroma.Config.get([:instance, :account_activation_required]) do + user + |> Pleroma.UserEmail.account_confirmation_email() + |> Pleroma.Mailer.deliver() + else + {:ok, :noop} + end + end + def needs_update?(%User{local: true}), do: false def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true @@ -211,14 +267,14 @@ defmodule Pleroma.User do end def maybe_direct_follow(%User{} = follower, %User{} = followed) do - if !User.ap_enabled?(followed) do + if not User.ap_enabled?(followed) do follow(follower, followed) else {:ok, follower} end end - def maybe_follow(%User{} = follower, %User{info: info} = followed) do + def maybe_follow(%User{} = follower, %User{info: _info} = followed) do if not following?(follower, followed) do follow(follower, followed) else @@ -280,10 +336,29 @@ defmodule Pleroma.User do end end + @spec following?(User.t(), User.t()) :: boolean def following?(%User{} = follower, %User{} = followed) do Enum.member?(follower.following, followed.follower_address) end + def follow_import(%User{} = follower, followed_identifiers) + when is_list(followed_identifiers) do + Enum.map( + followed_identifiers, + fn followed_identifier -> + with %User{} = followed <- get_or_fetch(followed_identifier), + {:ok, follower} <- maybe_direct_follow(follower, followed), + {:ok, _} <- ActivityPub.follow(follower, followed) do + followed + else + err -> + Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}") + err + end + end + ) + end + def locked?(%User{} = user) do user.info.locked || false end @@ -320,7 +395,11 @@ defmodule Pleroma.User do end def get_by_nickname(nickname) do - Repo.get_by(User, nickname: nickname) + Repo.get_by(User, nickname: nickname) || + if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do + [local_nickname, _] = String.split(nickname, "@") + Repo.get_by(User, nickname: local_nickname) + end end def get_by_nickname_or_email(nickname_or_email) do @@ -416,6 +495,7 @@ defmodule Pleroma.User do Enum.map(reqs, fn req -> req.actor end) |> Enum.uniq() |> Enum.map(fn ap_id -> get_by_ap_id(ap_id) end) + |> Enum.filter(fn u -> !is_nil(u) end) |> Enum.filter(fn u -> !following?(u, user) end) {:ok, users} @@ -549,6 +629,23 @@ defmodule Pleroma.User do Repo.all(q) end + def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do + Enum.map( + blocked_identifiers, + fn blocked_identifier -> + with %User{} = blocked <- get_or_fetch(blocked_identifier), + {:ok, blocker} <- block(blocker, blocked), + {:ok, _} <- ActivityPub.block(blocker, blocked) do + blocked + else + err -> + Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}") + err + end + end + ) + end + def block(blocker, %User{ap_id: ap_id} = blocked) do # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213) blocker = @@ -602,6 +699,9 @@ defmodule Pleroma.User do end) end + def blocked_users(user), + do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks)) + def block_domain(user, domain) do info_cng = user.info @@ -687,7 +787,9 @@ defmodule Pleroma.User do Pleroma.HTML.Scrubber.TwitterText end - def html_filter_policy(_), do: nil + @default_scrubbers Pleroma.Config.get([:markup, :scrub_policy]) + + def html_filter_policy(_), do: @default_scrubbers def get_or_fetch_by_ap_id(ap_id) do user = get_by_ap_id(ap_id) @@ -734,7 +836,8 @@ defmodule Pleroma.User do source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}} }) do key = - :public_key.pem_decode(public_key_pem) + public_key_pem + |> :public_key.pem_decode() |> hd() |> :public_key.pem_entry_decode() @@ -772,13 +875,10 @@ defmodule Pleroma.User do def ap_enabled?(%User{info: info}), do: info.ap_enabled def ap_enabled?(_), do: false - def get_or_fetch(uri_or_nickname) do - if String.starts_with?(uri_or_nickname, "http") do - get_or_fetch_by_ap_id(uri_or_nickname) - else - get_or_fetch_by_nickname(uri_or_nickname) - end - end + @doc "Gets or fetch a user by uri or nickname." + @spec get_or_fetch(String.t()) :: User.t() + def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri) + def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname) # wait a period of time and return newest version of the User structs # this is because we have synchronous follow APIs and need to simulate them @@ -804,7 +904,11 @@ defmodule Pleroma.User do end end - def parse_bio(bio, user \\ %User{info: %{source_data: %{}}}) do + def parse_bio(bio, user \\ %User{info: %{source_data: %{}}}) + def parse_bio(nil, _user), do: "" + def parse_bio(bio, _user) when bio == "", do: bio + + def parse_bio(bio, user) do mentions = Formatter.parse_mentions(bio) tags = Formatter.parse_tags(bio) @@ -815,7 +919,9 @@ defmodule Pleroma.User do {String.trim(name, ":"), url} end) - CommonUtils.format_input(bio, mentions, tags, "text/plain") |> Formatter.emojify(emoji) + bio + |> CommonUtils.format_input(mentions, tags, "text/plain") + |> Formatter.emojify(emoji) end def tag(user_identifiers, tags) when is_list(user_identifiers) do @@ -824,21 +930,23 @@ defmodule Pleroma.User do end) end + def tag(nickname, tags) when is_binary(nickname), + do: tag(User.get_by_nickname(nickname), tags) + + def tag(%User{} = user, tags), + do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags))) + def untag(user_identifiers, tags) when is_list(user_identifiers) do Repo.transaction(fn -> for user_identifier <- user_identifiers, do: untag(user_identifier, tags) end) end - def tag(nickname, tags) when is_binary(nickname), do: tag(User.get_by_nickname(nickname), tags) - def untag(nickname, tags) when is_binary(nickname), do: untag(User.get_by_nickname(nickname), tags) - def tag(%User{} = user, tags), - do: update_tags(user, Enum.uniq(user.tags ++ normalize_tags(tags))) - - def untag(%User{} = user, tags), do: update_tags(user, user.tags -- normalize_tags(tags)) + def untag(%User{} = user, tags), + do: update_tags(user, (user.tags || []) -- normalize_tags(tags)) defp update_tags(%User{} = user, new_tags) do {:ok, updated_user} = @@ -854,4 +962,12 @@ defmodule Pleroma.User do |> List.flatten() |> Enum.map(&String.downcase(&1)) end + + defp local_nickname_regex() do + if Pleroma.Config.get([:instance, :extended_nickname_format]) do + @extended_local_nickname_regex + else + @strict_local_nickname_regex + end + end end