user.ex 24.8 KB
Newer Older
1 2 3 4
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

lain's avatar
lain committed
5 6
defmodule Pleroma.User do
  use Ecto.Schema
7

8
  import Ecto.{Changeset, Query}
9
  alias Pleroma.{Repo, User, Object, Web, Activity, Notification}
10
  alias Comeonin.Pbkdf2
Maxim Filippov's avatar
Maxim Filippov committed
11 12
  alias Pleroma.Formatter
  alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
13
  alias Pleroma.Web.{OStatus, Websub, OAuth}
lain's avatar
lain committed
14
  alias Pleroma.Web.ActivityPub.{Utils, ActivityPub}
lain's avatar
lain committed
15

16 17
  require Logger

Maksim's avatar
Maksim committed
18 19
  @type t :: %__MODULE__{}

href's avatar
href committed
20 21 22
  @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]+$/
href's avatar
href committed
23
  @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
href's avatar
href committed
24

lain's avatar
lain committed
25
  schema "users" do
lain's avatar
lain committed
26 27 28 29 30 31 32 33 34 35 36 37
    field(:bio, :string)
    field(:email, :string)
    field(:name, :string)
    field(:nickname, :string)
    field(:password_hash, :string)
    field(:password, :string, virtual: true)
    field(:password_confirmation, :string, virtual: true)
    field(:following, {:array, :string}, default: [])
    field(:ap_id, :string)
    field(:avatar, :map)
    field(:local, :boolean, default: true)
    field(:follower_address, :string)
lain's avatar
lain committed
38
    field(:search_distance, :float, virtual: true)
39
    field(:tags, {:array, :string}, default: [])
40
    field(:last_refreshed_at, :naive_datetime)
lain's avatar
lain committed
41
    has_many(:notifications, Notification)
lain's avatar
lain committed
42
    embeds_one(:info, Pleroma.User.Info)
lain's avatar
lain committed
43 44 45

    timestamps()
  end
lain's avatar
lain committed
46

47 48 49 50
  def auth_active?(%User{} = user) do
    (user.info && !user.info.confirmation_pending) ||
      !Pleroma.Config.get([:instance, :account_activation_required])
  end
51 52

  def superuser?(%User{} = user), do: user.info && User.Info.superuser?(user.info)
53

lain's avatar
lain committed
54 55 56
  def avatar_url(user) do
    case user.avatar do
      %{"url" => [%{"href" => href} | _]} -> href
57
      _ -> "#{Web.base_url()}/images/avi.png"
lain's avatar
lain committed
58 59 60
    end
  end

lain's avatar
lain committed
61
  def banner_url(user) do
lain's avatar
lain committed
62
    case user.info.banner do
lain's avatar
lain committed
63
      %{"url" => [%{"href" => href} | _]} -> href
64
      _ -> "#{Web.base_url()}/images/banner.png"
lain's avatar
lain committed
65 66 67
    end
  end

lain's avatar
lain committed
68
  def profile_url(%User{info: %{source_data: %{"url" => url}}}), do: url
69 70 71
  def profile_url(%User{ap_id: ap_id}), do: ap_id
  def profile_url(_), do: nil

lain's avatar
lain committed
72
  def ap_id(%User{nickname: nickname}) do
lain's avatar
lain committed
73
    "#{Web.base_url()}/users/#{nickname}"
lain's avatar
lain committed
74 75 76 77 78
  end

  def ap_followers(%User{} = user) do
    "#{ap_id(user)}/followers"
  end
lain's avatar
lain committed
79 80 81 82 83 84 85

  def follow_changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:following])
    |> validate_required([:following])
  end

lain's avatar
lain committed
86
  def user_info(%User{} = user) do
87
    oneself = if user.local, do: 1, else: 0
lain's avatar
lain committed
88

lain's avatar
lain committed
89
    %{
90
      following_count: length(user.following) - oneself,
Ivan Tashkinov's avatar
Ivan Tashkinov committed
91 92 93 94 95
      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
lain's avatar
lain committed
96 97 98
    }
  end

lain's avatar
lain committed
99
  def remote_user_creation(params) do
lain's avatar
lain committed
100 101 102
    params =
      params
      |> Map.put(:info, params[:info] || %{})
lain's avatar
lain committed
103 104 105

    info_cng = User.Info.remote_user_creation(%User.Info{}, params[:info])

lain's avatar
lain committed
106
    changes =
lain's avatar
lain committed
107
      %User{}
lain's avatar
lain committed
108
      |> cast(params, [:bio, :name, :ap_id, :nickname, :avatar])
109
      |> validate_required([:name, :ap_id])
lain's avatar
lain committed
110 111 112 113 114
      |> unique_constraint(:nickname)
      |> validate_format(:nickname, @email_regex)
      |> validate_length(:bio, max: 5000)
      |> validate_length(:name, max: 100)
      |> put_change(:local, false)
lain's avatar
lain committed
115
      |> put_embed(:info, info_cng)
lain's avatar
lain committed
116

117
    if changes.valid? do
lain's avatar
lain committed
118
      case info_cng.changes[:source_data] do
lain's avatar
lain committed
119 120 121
        %{"followers" => followers} ->
          changes
          |> put_change(:follower_address, followers)
lain's avatar
lain committed
122

lain's avatar
lain committed
123 124
        _ ->
          followers = User.ap_followers(%User{nickname: changes.changes[:nickname]})
lain's avatar
lain committed
125

lain's avatar
lain committed
126 127 128
          changes
          |> put_change(:follower_address, followers)
      end
129 130 131
    else
      changes
    end
lain's avatar
lain committed
132 133
  end

lain's avatar
lain committed
134
  def update_changeset(struct, params \\ %{}) do
Thog's avatar
Thog committed
135
    struct
lain's avatar
lain committed
136
    |> cast(params, [:bio, :name, :avatar])
lain's avatar
lain committed
137
    |> unique_constraint(:nickname)
href's avatar
href committed
138
    |> validate_format(:nickname, local_nickname_regex())
lain's avatar
lain committed
139
    |> validate_length(:bio, max: 5000)
lain's avatar
lain committed
140 141 142
    |> validate_length(:name, min: 1, max: 100)
  end

lain's avatar
lain committed
143
  def upgrade_changeset(struct, params \\ %{}) do
144 145 146 147
    params =
      params
      |> Map.put(:last_refreshed_at, NaiveDateTime.utc_now())

lain's avatar
lain committed
148 149 150 151
    info_cng =
      struct.info
      |> User.Info.user_upgrade(params[:info])

lain's avatar
lain committed
152
    struct
lain's avatar
lain committed
153
    |> cast(params, [:bio, :name, :follower_address, :avatar, :last_refreshed_at])
lain's avatar
lain committed
154
    |> unique_constraint(:nickname)
href's avatar
href committed
155
    |> validate_format(:nickname, local_nickname_regex())
lain's avatar
lain committed
156 157
    |> validate_length(:bio, max: 5000)
    |> validate_length(:name, max: 100)
lain's avatar
lain committed
158
    |> put_embed(:info, info_cng)
lain's avatar
lain committed
159 160
  end

Roger Braun's avatar
Roger Braun committed
161
  def password_update_changeset(struct, params) do
lain's avatar
lain committed
162 163 164 165 166
    changeset =
      struct
      |> cast(params, [:password, :password_confirmation])
      |> validate_required([:password, :password_confirmation])
      |> validate_confirmation(:password)
Roger Braun's avatar
Roger Braun committed
167

168 169 170
    OAuth.Token.delete_user_tokens(struct)
    OAuth.Authorization.delete_user_authorizations(struct)

Roger Braun's avatar
Roger Braun committed
171 172
    if changeset.valid? do
      hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
lain's avatar
lain committed
173

Roger Braun's avatar
Roger Braun committed
174 175 176 177 178 179 180 181
      changeset
      |> put_change(:password_hash, hashed)
    else
      changeset
    end
  end

  def reset_password(user, data) do
lain's avatar
lain committed
182
    update_and_set_cache(password_update_changeset(user, data))
Roger Braun's avatar
Roger Braun committed
183 184
  end

185 186 187 188 189 190 191 192
  def register_changeset(struct, params \\ %{}, opts \\ []) do
    confirmation_status =
      if opts[:confirmed] || !Pleroma.Config.get([:instance, :account_activation_required]) do
        :confirmed
      else
        :unconfirmed
      end

Ivan Tashkinov's avatar
Ivan Tashkinov committed
193 194
    info_change = User.Info.confirmation_changeset(%User.Info{}, confirmation_status)

lain's avatar
lain committed
195 196 197 198 199 200 201
    changeset =
      struct
      |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
      |> validate_required([:email, :name, :nickname, :password, :password_confirmation])
      |> validate_confirmation(:password)
      |> unique_constraint(:email)
      |> unique_constraint(:nickname)
lain's avatar
lain committed
202
      |> validate_exclusion(:nickname, Pleroma.Config.get([Pleroma.User, :restricted_nicknames]))
href's avatar
href committed
203
      |> validate_format(:nickname, local_nickname_regex())
lain's avatar
lain committed
204 205 206
      |> validate_format(:email, @email_regex)
      |> validate_length(:bio, max: 1000)
      |> validate_length(:name, min: 1, max: 100)
Ivan Tashkinov's avatar
Ivan Tashkinov committed
207
      |> put_change(:info, info_change)
lain's avatar
lain committed
208 209

    if changeset.valid? do
210
      hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
lain's avatar
lain committed
211 212
      ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
      followers = User.ap_followers(%User{nickname: changeset.changes[:nickname]})
lain's avatar
lain committed
213

lain's avatar
lain committed
214 215 216 217
      changeset
      |> put_change(:password_hash, hashed)
      |> put_change(:ap_id, ap_id)
      |> put_change(:following, [followers])
218
      |> put_change(:follower_address, followers)
lain's avatar
lain committed
219 220 221 222 223
    else
      changeset
    end
  end

224 225
  @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
  def register(%Ecto.Changeset{} = changeset) do
Ivan Tashkinov's avatar
Ivan Tashkinov committed
226 227
    with {:ok, user} <- Repo.insert(changeset),
         {:ok, _} = try_send_confirmation_email(user) do
228 229 230 231
      {:ok, user}
    end
  end

232
  def try_send_confirmation_email(%User{} = user) do
233 234
    if user.info.confirmation_pending &&
         Pleroma.Config.get([:instance, :account_activation_required]) do
235 236 237 238 239 240 241 242
      user
      |> Pleroma.UserEmail.account_confirmation_email()
      |> Pleroma.Mailer.deliver()
    else
      {:ok, :noop}
    end
  end

243 244 245 246 247 248 249 250 251 252
  def needs_update?(%User{local: true}), do: false

  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
  end

  def needs_update?(_), do: true

lain's avatar
lain committed
253
  def maybe_direct_follow(%User{} = follower, %User{local: true, info: %{locked: true}}) do
254 255 256 257 258 259 260 261
    {:ok, follower}
  end

  def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
    follow(follower, followed)
  end

  def maybe_direct_follow(%User{} = follower, %User{} = followed) do
Maksim's avatar
Maksim committed
262
    if not User.ap_enabled?(followed) do
263
      follow(follower, followed)
264 265 266 267 268
    else
      {:ok, follower}
    end
  end

Maksim's avatar
Maksim committed
269
  def maybe_follow(%User{} = follower, %User{info: _info} = followed) do
270 271
    if not following?(follower, followed) do
      follow(follower, followed)
272
    else
273
      {:ok, follower}
274 275 276
    end
  end

lain's avatar
lain committed
277
  def follow(%User{} = follower, %User{info: info} = followed) do
278 279
    user_config = Application.get_env(:pleroma, :user)
    deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked)
280

281
    ap_followers = followed.follower_address
282

283
    cond do
lain's avatar
lain committed
284
      following?(follower, followed) or info.deactivated ->
285
        {:error, "Could not follow user: #{followed.nickname} is already on your list."}
lain's avatar
lain committed
286

287
      deny_follow_blocked and blocks?(followed, follower) ->
288
        {:error, "Could not follow user: #{followed.nickname} blocked you."}
lain's avatar
lain committed
289

290 291 292 293 294 295 296 297
      true ->
        if !followed.local && follower.local && !ap_enabled?(followed) do
          Websub.subscribe(follower, followed)
        end

        following =
          [ap_followers | follower.following]
          |> Enum.uniq()
298

299 300 301 302
        follower =
          follower
          |> follow_changeset(%{following: following})
          |> update_and_set_cache
303

304 305 306
        {:ok, _} = update_follower_count(followed)

        follower
307
    end
lain's avatar
lain committed
308
  end
lain's avatar
lain committed
309 310

  def unfollow(%User{} = follower, %User{} = followed) do
311
    ap_followers = followed.follower_address
lain's avatar
lain committed
312

313
    if following?(follower, followed) and follower.ap_id != followed.ap_id do
lain's avatar
lain committed
314 315 316
      following =
        follower.following
        |> List.delete(ap_followers)
lain's avatar
lain committed
317

lain's avatar
lain committed
318 319 320 321
      {:ok, follower} =
        follower
        |> follow_changeset(%{following: following})
        |> update_and_set_cache
322 323 324 325

      {:ok, followed} = update_follower_count(followed)

      {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
326
    else
327
      {:error, "Not subscribed!"}
328
    end
lain's avatar
lain committed
329
  end
330

Maksim's avatar
Maksim committed
331
  @spec following?(User.t(), User.t()) :: boolean
332
  def following?(%User{} = follower, %User{} = followed) do
333
    Enum.member?(follower.following, followed.follower_address)
334
  end
lain's avatar
lain committed
335

336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
  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

354
  def locked?(%User{} = user) do
355
    user.info.locked || false
356 357
  end

lain's avatar
lain committed
358 359 360 361
  def get_by_ap_id(ap_id) do
    Repo.get_by(User, ap_id: ap_id)
  end

lain's avatar
lain committed
362 363
  def update_and_set_cache(changeset) do
    with {:ok, user} <- Repo.update(changeset) do
Thog's avatar
Thog committed
364 365 366
      Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
      Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
      Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
lain's avatar
lain committed
367 368 369 370 371 372
      {:ok, user}
    else
      e -> e
    end
  end

lain's avatar
lain committed
373 374 375
  def invalidate_cache(user) do
    Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
    Cachex.del(:user_cache, "nickname:#{user.nickname}")
376
    Cachex.del(:user_cache, "user_info:#{user.id}")
lain's avatar
lain committed
377 378
  end

lain's avatar
lain committed
379
  def get_cached_by_ap_id(ap_id) do
380
    key = "ap_id:#{ap_id}"
Thog's avatar
Thog committed
381
    Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
lain's avatar
lain committed
382 383 384
  end

  def get_cached_by_nickname(nickname) do
385
    key = "nickname:#{nickname}"
Thog's avatar
Thog committed
386
    Cachex.fetch!(:user_cache, key, fn _ -> get_or_fetch_by_nickname(nickname) end)
lain's avatar
lain committed
387
  end
lain's avatar
lain committed
388

lain's avatar
lain committed
389
  def get_by_nickname(nickname) do
390 391 392 393 394
    Repo.get_by(User, nickname: nickname) ||
      if String.ends_with?(nickname, "@" <> Pleroma.Web.Endpoint.host()) do
        [local_nickname, _] = String.split(nickname, "@")
        Repo.get_by(User, nickname: local_nickname)
      end
395 396
  end

397 398 399 400 401 402 403
  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
  end

lain's avatar
lain committed
404 405
  def get_cached_user_info(user) do
    key = "user_info:#{user.id}"
Thog's avatar
Thog committed
406
    Cachex.fetch!(:user_cache, key, fn _ -> user_info(user) end)
lain's avatar
lain committed
407
  end
lain's avatar
lain committed
408

lain's avatar
lain committed
409 410 411 412 413 414 415 416 417
  def fetch_by_nickname(nickname) do
    ap_try = ActivityPub.make_user_from_nickname(nickname)

    case ap_try do
      {:ok, user} -> {:ok, user}
      _ -> OStatus.make_user(nickname)
    end
  end

lain's avatar
lain committed
418
  def get_or_fetch_by_nickname(nickname) do
lain's avatar
lain committed
419
    with %User{} = user <- get_by_nickname(nickname) do
lain's avatar
lain committed
420
      user
lain's avatar
lain committed
421 422 423 424 425 426 427 428
    else
      _e ->
        with [_nick, _domain] <- String.split(nickname, "@"),
             {:ok, user} <- fetch_by_nickname(nickname) do
          user
        else
          _e -> nil
        end
lain's avatar
lain committed
429
    end
lain's avatar
lain committed
430
  end
lain's avatar
lain committed
431

432 433 434 435 436 437 438 439 440 441
  def get_followers_query(%User{id: id, follower_address: follower_address}) do
    from(
      u in User,
      where: fragment("? <@ ?", ^[follower_address], u.following),
      where: u.id != ^id
    )
  end

  def get_followers(user) do
    q = get_followers_query(user)
lain's avatar
lain committed
442 443 444 445

    {:ok, Repo.all(q)}
  end

446 447 448 449 450 451 452 453 454 455
  def get_friends_query(%User{id: id, following: following}) do
    from(
      u in User,
      where: u.follower_address in ^following,
      where: u.id != ^id
    )
  end

  def get_friends(user) do
    q = get_friends_query(user)
lain's avatar
lain committed
456 457 458

    {:ok, Repo.all(q)}
  end
459

460 461 462
  def get_follow_requests_query(%User{} = user) do
    from(
      a in Activity,
kaniini's avatar
kaniini committed
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
      where:
        fragment(
          "? ->> 'type' = 'Follow'",
          a.data
        ),
      where:
        fragment(
          "? ->> 'state' = 'pending'",
          a.data
        ),
      where:
        fragment(
          "? @> ?",
          a.data,
          ^%{"object" => user.ap_id}
        )
479 480 481 482 483 484 485 486
    )
  end

  def get_follow_requests(%User{} = user) do
    q = get_follow_requests_query(user)
    reqs = Repo.all(q)

    users =
kaniini's avatar
kaniini committed
487 488 489
      Enum.map(reqs, fn req -> req.actor end)
      |> Enum.uniq()
      |> Enum.map(fn ap_id -> get_by_ap_id(ap_id) end)
490
      |> Enum.filter(fn u -> !following?(u, user) end)
491 492 493 494

    {:ok, users}
  end

495
  def increase_note_count(%User{} = user) do
lain's avatar
lain committed
496
    info_cng = User.Info.add_to_note_count(user.info, 1)
lain's avatar
lain committed
497 498 499 500

    cng =
      change(user)
      |> put_embed(:info, info_cng)
501

lain's avatar
lain committed
502
    update_and_set_cache(cng)
503 504
  end

505
  def decrease_note_count(%User{} = user) do
lain's avatar
lain committed
506
    info_cng = User.Info.add_to_note_count(user.info, -1)
lain's avatar
lain committed
507 508 509 510

    cng =
      change(user)
      |> put_embed(:info, info_cng)
511

lain's avatar
lain committed
512
    update_and_set_cache(cng)
513 514
  end

515
  def update_note_count(%User{} = user) do
lain's avatar
lain committed
516 517 518 519 520 521
    note_count_query =
      from(
        a in Object,
        where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
        select: count(a.id)
      )
522 523 524

    note_count = Repo.one(note_count_query)

lain's avatar
lain committed
525
    info_cng = User.Info.set_note_count(user.info, note_count)
526

lain's avatar
lain committed
527 528 529
    cng =
      change(user)
      |> put_embed(:info, info_cng)
530

lain's avatar
lain committed
531
    update_and_set_cache(cng)
532 533 534
  end

  def update_follower_count(%User{} = user) do
lain's avatar
lain committed
535 536 537 538 539 540 541
    follower_count_query =
      from(
        u in User,
        where: ^user.follower_address in u.following,
        where: u.id != ^user.id,
        select: count(u.id)
      )
542 543 544

    follower_count = Repo.one(follower_count_query)

lain's avatar
lain committed
545 546 547
    info_cng =
      user.info
      |> User.Info.set_follower_count(follower_count)
548

lain's avatar
lain committed
549 550 551
    cng =
      change(user)
      |> put_embed(:info, info_cng)
552

lain's avatar
lain committed
553
    update_and_set_cache(cng)
554
  end
555

556
  def get_users_from_set_query(ap_ids, false) do
557 558
    from(
      u in User,
559
      where: u.ap_id in ^ap_ids
560 561 562
    )
  end

563 564
  def get_users_from_set_query(ap_ids, true) do
    query = get_users_from_set_query(ap_ids, false)
565 566 567

    from(
      u in query,
568 569 570 571
      where: u.local == true
    )
  end

572 573 574 575 576
  def get_users_from_set(ap_ids, local_only \\ true) do
    get_users_from_set_query(ap_ids, local_only)
    |> Repo.all()
  end

577
  def get_recipients_from_activity(%Activity{recipients: to}) do
lain's avatar
lain committed
578 579 580 581 582 583
    query =
      from(
        u in User,
        where: u.ap_id in ^to,
        or_where: fragment("? && ?", u.following, ^to)
      )
584

lain's avatar
lain committed
585
    query = from(u in query, where: u.local == true)
586

587 588 589
    Repo.all(query)
  end

590
  def search(query, resolve \\ false) do
591 592 593
    # strip the beginning @ off if there is a query
    query = String.trim_leading(query, "@")

lain's avatar
lain committed
594 595 596
    if resolve do
      User.get_or_fetch_by_nickname(query)
    end
lain's avatar
lain committed
597

lain's avatar
lain committed
598
    inner =
lain's avatar
lain committed
599 600
      from(
        u in User,
lain's avatar
lain committed
601
        select_merge: %{
kaniini's avatar
kaniini committed
602 603 604 605 606 607 608
          search_distance:
            fragment(
              "? <-> (? || ?)",
              ^query,
              u.nickname,
              u.name
            )
609 610
        },
        where: not is_nil(u.nickname)
lain's avatar
lain committed
611 612
      )

kaniini's avatar
kaniini committed
613 614 615 616 617 618
    q =
      from(
        s in subquery(inner),
        order_by: s.search_distance,
        limit: 20
      )
lain's avatar
lain committed
619

lain's avatar
lain committed
620 621
    Repo.all(q)
  end
lain's avatar
lain committed
622

623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639
  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

640 641 642 643 644 645 646 647 648 649 650 651 652 653
  def block(blocker, %User{ap_id: ap_id} = blocked) do
    # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
    blocker =
      if following?(blocker, blocked) do
        {:ok, blocker, _} = unfollow(blocker, blocked)
        blocker
      else
        blocker
      end

    if following?(blocked, blocker) do
      unfollow(blocked, blocker)
    end

lain's avatar
lain committed
654 655 656
    info_cng =
      blocker.info
      |> User.Info.add_to_block(ap_id)
lain's avatar
lain committed
657

lain's avatar
lain committed
658 659 660 661 662
    cng =
      change(blocker)
      |> put_embed(:info, info_cng)

    update_and_set_cache(cng)
lain's avatar
lain committed
663 664
  end

665 666 667 668 669
  # helper to handle the block given only an actor's AP id
  def block(blocker, %{ap_id: ap_id}) do
    block(blocker, User.get_by_ap_id(ap_id))
  end

lain's avatar
lain committed
670 671 672 673
  def unblock(blocker, %{ap_id: ap_id}) do
    info_cng =
      blocker.info
      |> User.Info.remove_from_block(ap_id)
lain's avatar
lain committed
674

lain's avatar
lain committed
675 676 677 678 679
    cng =
      change(blocker)
      |> put_embed(:info, info_cng)

    update_and_set_cache(cng)
lain's avatar
lain committed
680 681 682
  end

  def blocks?(user, %{ap_id: ap_id}) do
lain's avatar
lain committed
683 684
    blocks = user.info.blocks
    domain_blocks = user.info.domain_blocks
eal's avatar
eal committed
685
    %{host: host} = URI.parse(ap_id)
eal's avatar
eal committed
686 687 688 689 690

    Enum.member?(blocks, ap_id) ||
      Enum.any?(domain_blocks, fn domain ->
        host == domain
      end)
eal's avatar
eal committed
691 692
  end

693 694 695
  def blocked_users(user),
    do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks))

eal's avatar
eal committed
696
  def block_domain(user, domain) do
lain's avatar
lain committed
697 698 699
    info_cng =
      user.info
      |> User.Info.add_to_domain_block(domain)
eal's avatar
eal committed
700

lain's avatar
lain committed
701 702 703
    cng =
      change(user)
      |> put_embed(:info, info_cng)
lain's avatar
lain committed
704 705

    update_and_set_cache(cng)
eal's avatar
eal committed
706 707 708
  end

  def unblock_domain(user, domain) do
lain's avatar
lain committed
709 710 711
    info_cng =
      user.info
      |> User.Info.remove_from_domain_block(domain)
eal's avatar
eal committed
712

lain's avatar
lain committed
713 714 715
    cng =
      change(user)
      |> put_embed(:info, info_cng)
lain's avatar
lain committed
716 717

    update_and_set_cache(cng)
lain's avatar
lain committed
718 719
  end

lain's avatar
lain committed
720
  def local_user_query() do
721 722 723 724 725
    from(
      u in User,
      where: u.local == true,
      where: not is_nil(u.nickname)
    )
lain's avatar
lain committed
726 727
  end

kaniini's avatar
kaniini committed
728 729 730 731 732 733 734 735
  def moderator_user_query() do
    from(
      u in User,
      where: u.local == true,
      where: fragment("?->'is_moderator' @> 'true'", u.info)
    )
  end

scarlett's avatar
scarlett committed
736
  def deactivate(%User{} = user, status \\ true) do
lain's avatar
lain committed
737
    info_cng = User.Info.set_activation_status(user.info, status)
lain's avatar
lain committed
738 739 740 741

    cng =
      change(user)
      |> put_embed(:info, info_cng)
lain's avatar
lain committed
742 743

    update_and_set_cache(cng)
lain's avatar
lain committed
744
  end
lain's avatar
lain committed
745

lain's avatar
lain committed
746
  def delete(%User{} = user) do
lain's avatar
lain committed
747 748 749
    {:ok, user} = User.deactivate(user)

    # Remove all relationships
lain's avatar
lain committed
750 751
    {:ok, followers} = User.get_followers(user)

lain's avatar
lain committed
752
    followers
lain's avatar
lain committed
753
    |> Enum.each(fn follower -> User.unfollow(follower, user) end)
lain's avatar
lain committed
754 755

    {:ok, friends} = User.get_friends(user)
lain's avatar
lain committed
756

lain's avatar
lain committed
757
    friends
lain's avatar
lain committed
758
    |> Enum.each(fn followed -> User.unfollow(user, followed) end)
lain's avatar
lain committed
759

lain's avatar
lain committed
760
    query = from(a in Activity, where: a.actor == ^user.ap_id)
lain's avatar
lain committed
761 762

    Repo.all(query)
lain's avatar
lain committed
763
    |> Enum.each(fn activity ->
lain's avatar
lain committed
764
      case activity.data["type"] do
lain's avatar
lain committed
765
        "Create" ->
766
          ActivityPub.delete(Object.normalize(activity.data["object"]))
lain's avatar
lain committed
767 768 769 770

        # TODO: Do something with likes, follows, repeats.
        _ ->
          "Doing nothing"
lain's avatar
lain committed
771 772 773
      end
    end)

774
    {:ok, user}
lain's avatar
lain committed
775
  end
776

lain's avatar
lain committed
777
  def html_filter_policy(%User{info: %{no_rich_text: true}}) do
kaniini's avatar
kaniini committed
778 779 780 781 782
    Pleroma.HTML.Scrubber.TwitterText
  end

  def html_filter_policy(_), do: nil

783
  def get_or_fetch_by_ap_id(ap_id) do
784 785 786
    user = get_by_ap_id(ap_id)

    if !is_nil(user) and !User.needs_update?(user) do
787 788
      user
    else
lain's avatar
lain committed
789 790 791
      ap_try = ActivityPub.make_user_from_ap_id(ap_id)

      case ap_try do
lain's avatar
lain committed
792 793 794
        {:ok, user} ->
          user

lain's avatar
lain committed
795 796 797
        _ ->
          case OStatus.make_user(ap_id) do
            {:ok, user} -> user
feld's avatar
feld committed
798
            _ -> {:error, "Could not fetch by AP id"}
lain's avatar
lain committed
799
          end
800 801 802 803
      end
    end
  end

804
  def get_or_create_instance_user do
805 806 807
    relay_uri = "#{Pleroma.Web.Endpoint.url()}/relay"

    if user = get_by_ap_id(relay_uri) do
808 809 810
      user
    else
      changes =
lain's avatar
lain committed
811
        %User{info: %User.Info{}}
812
        |> cast(%{}, [:ap_id, :nickname, :local])
813
        |> put_change(:ap_id, relay_uri)
814 815
        |> put_change(:nickname, nil)
        |> put_change(:local, true)
816
        |> put_change(:follower_address, relay_uri <> "/followers")
817 818 819 820 821 822

      {:ok, user} = Repo.insert(changes)
      user
    end
  end

823
  # AP style
lain's avatar
lain committed
824
  def public_key_from_info(%{
lain's avatar
lain committed
825
        source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}
lain's avatar
lain committed
826 827
      }) do
    key =
Maksim's avatar
Maksim committed
828 829
      public_key_pem
      |> :public_key.pem_decode()
lain's avatar
lain committed
830 831
      |> hd()
      |> :public_key.pem_entry_decode()
832

lain's avatar
lain committed
833
    {:ok, key}
834 835 836
  end

  # OStatus Magic Key
lain's avatar
lain committed
837
  def public_key_from_info(%{magic_key: magic_key}) do
838 839 840
    {:ok, Pleroma.Web.Salmon.decode_key(magic_key)}
  end

841
  def get_public_key_for_ap_id(ap_id) do
842 843
    with %User{} = user <- get_or_fetch_by_ap_id(ap_id),
         {:ok, public_key} <- public_key_from_info(user.info) do
844 845 846 847 848
      {:ok, public_key}
    else
      _ -> :error
    end
  end
lain's avatar
lain committed
849

850 851 852
  defp blank?(""), do: nil
  defp blank?(n), do: n

lain's avatar
lain committed
853
  def insert_or_update_user(data) do
lain's avatar
lain committed
854 855 856 857
    data =
      data
      |> Map.put(:name, blank?(data[:name]) || data[:nickname])

lain's avatar
lain committed
858
    cs = User.remote_user_creation(data)
lain's avatar
lain committed
859

lain's avatar
lain committed
860 861
    Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname)
  end
862

863
  def ap_enabled?(%User{local: true}), do: true
lain's avatar
lain committed
864
  def ap_enabled?(%User{info: info}), do: info.ap_enabled
lain's avatar
lain committed
865
  def ap_enabled?(_), do: false
lain's avatar
lain committed
866

Maksim's avatar
Maksim committed
867 868 869 870
  @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)
871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894

  # 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
  # with an async handshake
  def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
    with %User{} = a <- Repo.get(User, a.id),
         %User{} = b <- Repo.get(User, b.id) do
      {:ok, a, b}
    else
      _e ->
        :error
    end
  end

  def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
    with :ok <- :timer.sleep(timeout),
         %User{} = a <- Repo.get(User, a.id),
         %User{} = b <- Repo.get(User, b.id) do
      {:ok, a, b}
    else
      _e ->
        :error
    end
  end
Maxim Filippov's avatar
Maxim Filippov committed
895

896
  def parse_bio(bio, user \\ %User{info: %{source_data: %{}}})
lain's avatar
lain committed
897 898
  def parse_bio(nil, _user), do: ""
  def parse_bio(bio, _user) when bio == "", do: bio
899 900

  def parse_bio(bio, user) do
Maxim Filippov's avatar
Maxim Filippov committed
901 902 903 904 905 906 907 908 909 910
    mentions = Formatter.parse_mentions(bio)
    tags = Formatter.parse_tags(bio)

    emoji =
      (user.info.source_data["tag"] || [])
      |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
      |> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
        {String.trim(name, ":"), url}
      end)

Maksim's avatar
Maksim committed
911 912 913
    bio
    |> CommonUtils.format_input(mentions, tags, "text/plain")
    |> Formatter.emojify(emoji)
Maxim Filippov's avatar
Maxim Filippov committed
914
  end
915

916 917 918 919 920
  def tag(user_identifiers, tags) when is_list(user_identifiers) do
    Repo.transaction(fn ->
      for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
    end)
  end
921

Maksim's avatar
Maksim committed
922 923 924 925
  def tag(nickname, tags) when is_binary(nickname),
    do: tag(User.get_by_nickname(nickname), tags)

  def tag(%User{} = user, tags),
926
    do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
Maksim's avatar
Maksim committed
927

928 929 930 931 932
  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
933

934 935
  def untag(nickname, tags) when is_binary(nickname),
    do: untag(User.get_by_nickname(nickname), tags)
936

937 938
  def untag(%User{} = user, tags),
    do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
939

940 941 942 943 944
  defp update_tags(%User{} = user, new_tags) do
    {:ok, updated_user} =
      user
      |> change(%{tags: new_tags})
      |> Repo.update()
945

946
    updated_user
947
  end
Ivan Tashkinov's avatar
Ivan Tashkinov committed
948

949 950 951 952 953
  defp normalize_tags(tags) do
    [tags]
    |> List.flatten()
    |> Enum.map(&String.downcase(&1))
  end