user.ex 25.6 KB
Newer Older
1
# Pleroma: A lightweight social networking server
kaniini's avatar
kaniini committed
2
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 4
# 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 53
  def remote_or_auth_active?(%User{local: false}), do: true
  def remote_or_auth_active?(%User{local: true} = user), do: auth_active?(user)
54

55 56 57 58 59 60
  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
61
  def superuser?(%User{} = user), do: user.info && User.Info.superuser?(user.info)
62

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

lain's avatar
lain committed
70
  def banner_url(user) do
lain's avatar
lain committed
71
    case user.info.banner do
lain's avatar
lain committed
72
      %{"url" => [%{"href" => href} | _]} -> href
73
      _ -> "#{Web.base_url()}/images/banner.png"
lain's avatar
lain committed
74 75 76
    end
  end

lain's avatar
lain committed
77
  def profile_url(%User{info: %{source_data: %{"url" => url}}}), do: url
78 79 80
  def profile_url(%User{ap_id: ap_id}), do: ap_id
  def profile_url(_), do: nil

lain's avatar
lain committed
81
  def ap_id(%User{nickname: nickname}) do
lain's avatar
lain committed
82
    "#{Web.base_url()}/users/#{nickname}"
lain's avatar
lain committed
83 84 85 86 87
  end

  def ap_followers(%User{} = user) do
    "#{ap_id(user)}/followers"
  end
lain's avatar
lain committed
88 89 90 91 92 93 94

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

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

lain's avatar
lain committed
98
    %{
99
      following_count: length(user.following) - oneself,
Ivan Tashkinov's avatar
Ivan Tashkinov committed
100 101 102 103 104
      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
105 106 107
    }
  end

lain's avatar
lain committed
108
  def remote_user_creation(params) do
lain's avatar
lain committed
109 110 111
    params =
      params
      |> Map.put(:info, params[:info] || %{})
lain's avatar
lain committed
112 113 114

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

lain's avatar
lain committed
115
    changes =
lain's avatar
lain committed
116
      %User{}
lain's avatar
lain committed
117
      |> cast(params, [:bio, :name, :ap_id, :nickname, :avatar])
118
      |> validate_required([:name, :ap_id])
lain's avatar
lain committed
119 120 121 122 123
      |> 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
124
      |> put_embed(:info, info_cng)
lain's avatar
lain committed
125

126
    if changes.valid? do
lain's avatar
lain committed
127
      case info_cng.changes[:source_data] do
lain's avatar
lain committed
128 129 130
        %{"followers" => followers} ->
          changes
          |> put_change(:follower_address, followers)
lain's avatar
lain committed
131

lain's avatar
lain committed
132 133
        _ ->
          followers = User.ap_followers(%User{nickname: changes.changes[:nickname]})
lain's avatar
lain committed
134

lain's avatar
lain committed
135 136 137
          changes
          |> put_change(:follower_address, followers)
      end
138 139 140
    else
      changes
    end
lain's avatar
lain committed
141 142
  end

lain's avatar
lain committed
143
  def update_changeset(struct, params \\ %{}) do
Thog's avatar
Thog committed
144
    struct
lain's avatar
lain committed
145
    |> cast(params, [:bio, :name, :avatar])
lain's avatar
lain committed
146
    |> unique_constraint(:nickname)
href's avatar
href committed
147
    |> validate_format(:nickname, local_nickname_regex())
lain's avatar
lain committed
148
    |> validate_length(:bio, max: 5000)
lain's avatar
lain committed
149 150 151
    |> validate_length(:name, min: 1, max: 100)
  end

lain's avatar
lain committed
152
  def upgrade_changeset(struct, params \\ %{}) do
153 154 155 156
    params =
      params
      |> Map.put(:last_refreshed_at, NaiveDateTime.utc_now())

lain's avatar
lain committed
157 158 159 160
    info_cng =
      struct.info
      |> User.Info.user_upgrade(params[:info])

lain's avatar
lain committed
161
    struct
lain's avatar
lain committed
162
    |> cast(params, [:bio, :name, :follower_address, :avatar, :last_refreshed_at])
lain's avatar
lain committed
163
    |> unique_constraint(:nickname)
href's avatar
href committed
164
    |> validate_format(:nickname, local_nickname_regex())
lain's avatar
lain committed
165 166
    |> validate_length(:bio, max: 5000)
    |> validate_length(:name, max: 100)
lain's avatar
lain committed
167
    |> put_embed(:info, info_cng)
lain's avatar
lain committed
168 169
  end

Roger Braun's avatar
Roger Braun committed
170
  def password_update_changeset(struct, params) do
lain's avatar
lain committed
171 172 173 174 175
    changeset =
      struct
      |> cast(params, [:password, :password_confirmation])
      |> validate_required([:password, :password_confirmation])
      |> validate_confirmation(:password)
Roger Braun's avatar
Roger Braun committed
176

177 178 179
    OAuth.Token.delete_user_tokens(struct)
    OAuth.Authorization.delete_user_authorizations(struct)

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

Roger Braun's avatar
Roger Braun committed
183 184 185 186 187 188 189 190
      changeset
      |> put_change(:password_hash, hashed)
    else
      changeset
    end
  end

  def reset_password(user, data) do
lain's avatar
lain committed
191
    update_and_set_cache(password_update_changeset(user, data))
Roger Braun's avatar
Roger Braun committed
192 193
  end

194 195 196 197 198 199 200 201
  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
202 203
    info_change = User.Info.confirmation_changeset(%User.Info{}, confirmation_status)

lain's avatar
lain committed
204 205 206 207 208 209 210
    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
211
      |> validate_exclusion(:nickname, Pleroma.Config.get([Pleroma.User, :restricted_nicknames]))
href's avatar
href committed
212
      |> validate_format(:nickname, local_nickname_regex())
lain's avatar
lain committed
213 214 215
      |> validate_format(:email, @email_regex)
      |> validate_length(:bio, max: 1000)
      |> validate_length(:name, min: 1, max: 100)
Ivan Tashkinov's avatar
Ivan Tashkinov committed
216
      |> put_change(:info, info_change)
lain's avatar
lain committed
217 218

    if changeset.valid? do
219
      hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
lain's avatar
lain committed
220 221
      ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
      followers = User.ap_followers(%User{nickname: changeset.changes[:nickname]})
lain's avatar
lain committed
222

lain's avatar
lain committed
223 224 225 226
      changeset
      |> put_change(:password_hash, hashed)
      |> put_change(:ap_id, ap_id)
      |> put_change(:following, [followers])
227
      |> put_change(:follower_address, followers)
lain's avatar
lain committed
228 229 230 231 232
    else
      changeset
    end
  end

233 234
  @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
235 236
    with {:ok, user} <- Repo.insert(changeset),
         {:ok, _} = try_send_confirmation_email(user) do
237 238 239 240
      {:ok, user}
    end
  end

241
  def try_send_confirmation_email(%User{} = user) do
242 243
    if user.info.confirmation_pending &&
         Pleroma.Config.get([:instance, :account_activation_required]) do
244 245 246 247 248 249 250 251
      user
      |> Pleroma.UserEmail.account_confirmation_email()
      |> Pleroma.Mailer.deliver()
    else
      {:ok, :noop}
    end
  end

252 253 254 255 256 257 258 259 260 261
  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
262
  def maybe_direct_follow(%User{} = follower, %User{local: true, info: %{locked: true}}) do
263 264 265 266 267 268 269 270
    {: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
271
    if not User.ap_enabled?(followed) do
272
      follow(follower, followed)
273 274 275 276 277
    else
      {:ok, follower}
    end
  end

Maksim's avatar
Maksim committed
278
  def maybe_follow(%User{} = follower, %User{info: _info} = followed) do
279 280
    if not following?(follower, followed) do
      follow(follower, followed)
281
    else
282
      {:ok, follower}
283 284 285
    end
  end

lain's avatar
lain committed
286
  def follow(%User{} = follower, %User{info: info} = followed) do
287 288
    user_config = Application.get_env(:pleroma, :user)
    deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked)
289

290
    ap_followers = followed.follower_address
291

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

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

299 300 301 302 303 304 305 306
      true ->
        if !followed.local && follower.local && !ap_enabled?(followed) do
          Websub.subscribe(follower, followed)
        end

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

308 309 310 311
        follower =
          follower
          |> follow_changeset(%{following: following})
          |> update_and_set_cache
312

313 314 315
        {:ok, _} = update_follower_count(followed)

        follower
316
    end
lain's avatar
lain committed
317
  end
lain's avatar
lain committed
318 319

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

322
    if following?(follower, followed) and follower.ap_id != followed.ap_id do
lain's avatar
lain committed
323 324 325
      following =
        follower.following
        |> List.delete(ap_followers)
lain's avatar
lain committed
326

lain's avatar
lain committed
327 328 329 330
      {:ok, follower} =
        follower
        |> follow_changeset(%{following: following})
        |> update_and_set_cache
331 332 333 334

      {:ok, followed} = update_follower_count(followed)

      {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
335
    else
336
      {:error, "Not subscribed!"}
337
    end
lain's avatar
lain committed
338
  end
339

Maksim's avatar
Maksim committed
340
  @spec following?(User.t(), User.t()) :: boolean
341
  def following?(%User{} = follower, %User{} = followed) do
342
    Enum.member?(follower.following, followed.follower_address)
343
  end
lain's avatar
lain committed
344

345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
  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

363
  def locked?(%User{} = user) do
364
    user.info.locked || false
365 366
  end

lain's avatar
lain committed
367 368 369 370
  def get_by_ap_id(ap_id) do
    Repo.get_by(User, ap_id: ap_id)
  end

371 372 373 374 375 376 377 378 379
  # 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, "/"))
    nickname = "#{name}@#{domain}"

    get_by_nickname(nickname)
  end

lain's avatar
lain committed
380 381
  def update_and_set_cache(changeset) do
    with {:ok, user} <- Repo.update(changeset) do
Thog's avatar
Thog committed
382 383 384
      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
385 386 387 388 389 390
      {:ok, user}
    else
      e -> e
    end
  end

lain's avatar
lain committed
391 392 393
  def invalidate_cache(user) do
    Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
    Cachex.del(:user_cache, "nickname:#{user.nickname}")
394
    Cachex.del(:user_cache, "user_info:#{user.id}")
lain's avatar
lain committed
395 396
  end

lain's avatar
lain committed
397
  def get_cached_by_ap_id(ap_id) do
398
    key = "ap_id:#{ap_id}"
Thog's avatar
Thog committed
399
    Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
lain's avatar
lain committed
400 401 402
  end

  def get_cached_by_nickname(nickname) do
403
    key = "nickname:#{nickname}"
Thog's avatar
Thog committed
404
    Cachex.fetch!(:user_cache, key, fn _ -> get_or_fetch_by_nickname(nickname) end)
lain's avatar
lain committed
405
  end
lain's avatar
lain committed
406

lain's avatar
lain committed
407
  def get_by_nickname(nickname) do
408
    Repo.get_by(User, nickname: nickname) ||
409
      if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
410 411 412
        [local_nickname, _] = String.split(nickname, "@")
        Repo.get_by(User, nickname: local_nickname)
      end
413 414
  end

415 416 417 418 419 420 421
  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
422 423
  def get_cached_user_info(user) do
    key = "user_info:#{user.id}"
Thog's avatar
Thog committed
424
    Cachex.fetch!(:user_cache, key, fn _ -> user_info(user) end)
lain's avatar
lain committed
425
  end
lain's avatar
lain committed
426

lain's avatar
lain committed
427 428 429 430 431 432 433 434 435
  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
436
  def get_or_fetch_by_nickname(nickname) do
lain's avatar
lain committed
437
    with %User{} = user <- get_by_nickname(nickname) do
lain's avatar
lain committed
438
      user
lain's avatar
lain committed
439 440 441 442 443 444 445 446
    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
447
    end
lain's avatar
lain committed
448
  end
lain's avatar
lain committed
449

450 451 452 453 454 455 456 457 458 459
  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
460 461 462 463

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

464 465 466 467 468 469 470 471 472 473
  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
474 475 476

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

478 479 480
  def get_follow_requests_query(%User{} = user) do
    from(
      a in Activity,
kaniini's avatar
kaniini committed
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496
      where:
        fragment(
          "? ->> 'type' = 'Follow'",
          a.data
        ),
      where:
        fragment(
          "? ->> 'state' = 'pending'",
          a.data
        ),
      where:
        fragment(
          "? @> ?",
          a.data,
          ^%{"object" => user.ap_id}
        )
497 498 499 500 501 502 503 504
    )
  end

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

    users =
kaniini's avatar
kaniini committed
505 506 507
      Enum.map(reqs, fn req -> req.actor end)
      |> Enum.uniq()
      |> Enum.map(fn ap_id -> get_by_ap_id(ap_id) end)
508
      |> Enum.filter(fn u -> !is_nil(u) end)
509
      |> Enum.filter(fn u -> !following?(u, user) end)
510 511 512 513

    {:ok, users}
  end

514
  def increase_note_count(%User{} = user) do
lain's avatar
lain committed
515
    info_cng = User.Info.add_to_note_count(user.info, 1)
lain's avatar
lain committed
516 517 518 519

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

lain's avatar
lain committed
521
    update_and_set_cache(cng)
522 523
  end

524
  def decrease_note_count(%User{} = user) do
lain's avatar
lain committed
525
    info_cng = User.Info.add_to_note_count(user.info, -1)
lain's avatar
lain committed
526 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
  end

534
  def update_note_count(%User{} = user) do
lain's avatar
lain committed
535 536 537 538 539 540
    note_count_query =
      from(
        a in Object,
        where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
        select: count(a.id)
      )
541 542 543

    note_count = Repo.one(note_count_query)

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

lain's avatar
lain committed
546 547 548
    cng =
      change(user)
      |> put_embed(:info, info_cng)
549

lain's avatar
lain committed
550
    update_and_set_cache(cng)
551 552 553
  end

  def update_follower_count(%User{} = user) do
lain's avatar
lain committed
554 555 556 557 558 559 560
    follower_count_query =
      from(
        u in User,
        where: ^user.follower_address in u.following,
        where: u.id != ^user.id,
        select: count(u.id)
      )
561 562 563

    follower_count = Repo.one(follower_count_query)

lain's avatar
lain committed
564 565 566
    info_cng =
      user.info
      |> User.Info.set_follower_count(follower_count)
567

lain's avatar
lain committed
568 569 570
    cng =
      change(user)
      |> put_embed(:info, info_cng)
571

lain's avatar
lain committed
572
    update_and_set_cache(cng)
573
  end
574

575
  def get_users_from_set_query(ap_ids, false) do
576 577
    from(
      u in User,
578
      where: u.ap_id in ^ap_ids
579 580 581
    )
  end

582 583
  def get_users_from_set_query(ap_ids, true) do
    query = get_users_from_set_query(ap_ids, false)
584 585 586

    from(
      u in query,
587 588 589 590
      where: u.local == true
    )
  end

591 592 593 594 595
  def get_users_from_set(ap_ids, local_only \\ true) do
    get_users_from_set_query(ap_ids, local_only)
    |> Repo.all()
  end

596
  def get_recipients_from_activity(%Activity{recipients: to}) do
lain's avatar
lain committed
597 598 599 600 601 602
    query =
      from(
        u in User,
        where: u.ap_id in ^to,
        or_where: fragment("? && ?", u.following, ^to)
      )
603

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

606 607 608
    Repo.all(query)
  end

609
  def search(query, resolve \\ false) do
610 611 612
    # strip the beginning @ off if there is a query
    query = String.trim_leading(query, "@")

lain's avatar
lain committed
613 614 615
    if resolve do
      User.get_or_fetch_by_nickname(query)
    end
lain's avatar
lain committed
616

lain's avatar
lain committed
617
    inner =
lain's avatar
lain committed
618 619
      from(
        u in User,
lain's avatar
lain committed
620
        select_merge: %{
kaniini's avatar
kaniini committed
621 622
          search_distance:
            fragment(
cascode's avatar
cascode committed
623
              "? <-> (? || coalesce(?, ''))",
kaniini's avatar
kaniini committed
624 625 626 627
              ^query,
              u.nickname,
              u.name
            )
628 629
        },
        where: not is_nil(u.nickname)
lain's avatar
lain committed
630 631
      )

kaniini's avatar
kaniini committed
632 633 634 635 636 637
    q =
      from(
        s in subquery(inner),
        order_by: s.search_distance,
        limit: 20
      )
lain's avatar
lain committed
638

lain's avatar
lain committed
639 640
    Repo.all(q)
  end
lain's avatar
lain committed
641

642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658
  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

659 660 661 662 663 664 665 666 667 668 669 670 671 672
  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
673 674 675
    info_cng =
      blocker.info
      |> User.Info.add_to_block(ap_id)
lain's avatar
lain committed
676

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

    update_and_set_cache(cng)
lain's avatar
lain committed
682 683
  end

684 685 686 687 688
  # 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
689 690 691 692
  def unblock(blocker, %{ap_id: ap_id}) do
    info_cng =
      blocker.info
      |> User.Info.remove_from_block(ap_id)
lain's avatar
lain committed
693

lain's avatar
lain committed
694 695 696 697 698
    cng =
      change(blocker)
      |> put_embed(:info, info_cng)

    update_and_set_cache(cng)
lain's avatar
lain committed
699 700 701
  end

  def blocks?(user, %{ap_id: ap_id}) do
lain's avatar
lain committed
702 703
    blocks = user.info.blocks
    domain_blocks = user.info.domain_blocks
eal's avatar
eal committed
704
    %{host: host} = URI.parse(ap_id)
eal's avatar
eal committed
705 706 707 708 709

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

712 713 714
  def blocked_users(user),
    do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks))

eal's avatar
eal committed
715
  def block_domain(user, domain) do
lain's avatar
lain committed
716 717 718
    info_cng =
      user.info
      |> User.Info.add_to_domain_block(domain)
eal's avatar
eal committed
719

lain's avatar
lain committed
720 721 722
    cng =
      change(user)
      |> put_embed(:info, info_cng)
lain's avatar
lain committed
723 724

    update_and_set_cache(cng)
eal's avatar
eal committed
725 726 727
  end

  def unblock_domain(user, domain) do
lain's avatar
lain committed
728 729 730
    info_cng =
      user.info
      |> User.Info.remove_from_domain_block(domain)
eal's avatar
eal committed
731

lain's avatar
lain committed
732 733 734
    cng =
      change(user)
      |> put_embed(:info, info_cng)
lain's avatar
lain committed
735 736

    update_and_set_cache(cng)
lain's avatar
lain committed
737 738
  end

lain's avatar
lain committed
739
  def local_user_query() do
740 741 742 743 744
    from(
      u in User,
      where: u.local == true,
      where: not is_nil(u.nickname)
    )
lain's avatar
lain committed
745 746
  end

kaniini's avatar
kaniini committed
747 748 749 750 751 752 753 754
  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
755
  def deactivate(%User{} = user, status \\ true) do
lain's avatar
lain committed
756
    info_cng = User.Info.set_activation_status(user.info, status)
lain's avatar
lain committed
757 758 759 760

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

    update_and_set_cache(cng)
lain's avatar
lain committed
763
  end
lain's avatar
lain committed
764

lain's avatar
lain committed
765
  def delete(%User{} = user) do
lain's avatar
lain committed
766 767 768
    {:ok, user} = User.deactivate(user)

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

lain's avatar
lain committed
771
    followers
lain's avatar
lain committed
772
    |> Enum.each(fn follower -> User.unfollow(follower, user) end)
lain's avatar
lain committed
773 774

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

lain's avatar
lain committed
776
    friends
lain's avatar
lain committed
777
    |> Enum.each(fn followed -> User.unfollow(user, followed) end)
lain's avatar
lain committed
778

lain's avatar
lain committed
779
    query = from(a in Activity, where: a.actor == ^user.ap_id)
lain's avatar
lain committed
780 781

    Repo.all(query)
lain's avatar
lain committed
782
    |> Enum.each(fn activity ->
lain's avatar
lain committed
783
      case activity.data["type"] do
lain's avatar
lain committed
784
        "Create" ->
785
          ActivityPub.delete(Object.normalize(activity.data["object"]))
lain's avatar
lain committed
786 787 788 789

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

793
    {:ok, user}
lain's avatar
lain committed
794
  end
795

lain's avatar
lain committed
796
  def html_filter_policy(%User{info: %{no_rich_text: true}}) do
kaniini's avatar
kaniini committed
797 798 799
    Pleroma.HTML.Scrubber.TwitterText
  end

800 801 802
  @default_scrubbers Pleroma.Config.get([:markup, :scrub_policy])

  def html_filter_policy(_), do: @default_scrubbers
kaniini's avatar
kaniini committed
803

804
  def get_or_fetch_by_ap_id(ap_id) do
805 806 807
    user = get_by_ap_id(ap_id)

    if !is_nil(user) and !User.needs_update?(user) do
808 809
      user
    else
lain's avatar
lain committed
810 811 812
      ap_try = ActivityPub.make_user_from_ap_id(ap_id)

      case ap_try do
lain's avatar
lain committed
813 814 815
        {:ok, user} ->
          user

lain's avatar
lain committed
816 817 818
        _ ->
          case OStatus.make_user(ap_id) do
            {:ok, user} -> user
feld's avatar
feld committed
819
            _ -> {:error, "Could not fetch by AP id"}
lain's avatar
lain committed
820
          end
821 822 823 824
      end
    end
  end

825
  def get_or_create_instance_user do
826 827 828
    relay_uri = "#{Pleroma.Web.Endpoint.url()}/relay"

    if user = get_by_ap_id(relay_uri) do
829 830 831
      user
    else
      changes =
lain's avatar
lain committed
832
        %User{info: %User.Info{}}
833
        |> cast(%{}, [:ap_id, :nickname, :local])
834
        |> put_change(:ap_id, relay_uri)
835 836
        |> put_change(:nickname, nil)
        |> put_change(:local, true)
837
        |> put_change(:follower_address, relay_uri <> "/followers")
838 839 840 841 842 843

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

844
  # AP style
lain's avatar
lain committed
845
  def public_key_from_info(%{
lain's avatar
lain committed
846
        source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}
lain's avatar
lain committed
847 848
      }) do
    key =
Maksim's avatar
Maksim committed
849 850
      public_key_pem
      |> :public_key.pem_decode()
lain's avatar
lain committed
851 852
      |> hd()
      |> :public_key.pem_entry_decode()
853

lain's avatar
lain committed
854
    {:ok, key}
855 856 857
  end

  # OStatus Magic Key
lain's avatar
lain committed
858
  def public_key_from_info(%{magic_key: magic_key}) do
859 860 861
    {:ok, Pleroma.Web.Salmon.decode_key(magic_key)}
  end

862
  def get_public_key_for_ap_id(ap_id) do
863 864
    with %User{} = user <- get_or_fetch_by_ap_id(ap_id),
         {:ok, public_key} <- public_key_from_info(user.info) do
865 866 867 868 869
      {:ok, public_key}
    else
      _ -> :error
    end
  end
lain's avatar
lain committed
870

871 872 873
  defp blank?(""), do: nil
  defp blank?(n), do: n

lain's avatar
lain committed
874
  def insert_or_update_user(data) do
lain's avatar
lain committed
875 876 877 878
    data =
      data
      |> Map.put(:name, blank?(data[:name]) || data[:nickname])

lain's avatar
lain committed
879
    cs = User.remote_user_creation(data)
lain's avatar
lain committed
880

lain's avatar
lain committed
881 882
    Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname)
  end
883

884
  def ap_enabled?(%User{local: true}), do: true
lain's avatar
lain committed
885
  def ap_enabled?(%User{info: info}), do: info.ap_enabled
lain's avatar
lain committed
886
  def ap_enabled?(_), do: false
lain's avatar
lain committed
887

Maksim's avatar
Maksim committed
888 889 890 891
  @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)
892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915

  # 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
916

917
  def parse_bio(bio, user \\ %User{info: %{source_data: %{}}})
lain's avatar
lain committed
918 919
  def parse_bio(nil, _user), do: ""
  def parse_bio(bio, _user) when bio == "", do: bio
920 921

  def parse_bio(bio, user) do
Maxim Filippov's avatar
Maxim Filippov committed
922 923 924 925 926 927 928 929 930 931
    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
932 933 934
    bio
    |> CommonUtils.format_input(mentions, tags, "text/plain")
    |> Formatter.emojify(emoji)
Maxim Filippov's avatar
Maxim Filippov committed
935
  end
936

937 938 939 940 941
  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
942

Maksim's avatar
Maksim committed
943 944 945 946
  def tag(nickname, tags) when is_binary(nickname),
    do: tag(User.get_by_nickname(nickname), tags)

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