user.ex 23 KB
Newer Older
lain's avatar
lain committed
1 2
defmodule Pleroma.User do
  use Ecto.Schema
3

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

Maksim's avatar
Maksim committed
12 13
  @type t :: %__MODULE__{}

href's avatar
href committed
14 15 16
  @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
17
  @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
href's avatar
href committed
18

lain's avatar
lain committed
19
  schema "users" do
lain's avatar
lain committed
20 21 22 23 24 25 26 27 28 29 30 31
    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
32
    field(:search_distance, :float, virtual: true)
33
    field(:tags, {:array, :string}, default: [])
34
    field(:last_refreshed_at, :naive_datetime)
lain's avatar
lain committed
35
    has_many(:notifications, Notification)
lain's avatar
lain committed
36
    embeds_one(:info, Pleroma.User.Info)
lain's avatar
lain committed
37 38 39

    timestamps()
  end
lain's avatar
lain committed
40

41 42 43
  def auth_active?(%User{} = user), do: user.info && !user.info.confirmation_pending

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

lain's avatar
lain committed
45 46 47
  def avatar_url(user) do
    case user.avatar do
      %{"url" => [%{"href" => href} | _]} -> href
48
      _ -> "#{Web.base_url()}/images/avi.png"
lain's avatar
lain committed
49 50 51
    end
  end

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

lain's avatar
lain committed
59
  def profile_url(%User{info: %{source_data: %{"url" => url}}}), do: url
60 61 62
  def profile_url(%User{ap_id: ap_id}), do: ap_id
  def profile_url(_), do: nil

lain's avatar
lain committed
63
  def ap_id(%User{nickname: nickname}) do
lain's avatar
lain committed
64
    "#{Web.base_url()}/users/#{nickname}"
lain's avatar
lain committed
65 66 67 68 69
  end

  def ap_followers(%User{} = user) do
    "#{ap_id(user)}/followers"
  end
lain's avatar
lain committed
70 71 72 73 74 75 76

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

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

lain's avatar
lain committed
80
    %{
81
      following_count: length(user.following) - oneself,
Ivan Tashkinov's avatar
Ivan Tashkinov committed
82 83 84 85 86
      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
87 88 89
    }
  end

lain's avatar
lain committed
90
  def remote_user_creation(params) do
lain's avatar
lain committed
91 92 93
    params =
      params
      |> Map.put(:info, params[:info] || %{})
lain's avatar
lain committed
94 95 96

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

lain's avatar
lain committed
97
    changes =
lain's avatar
lain committed
98
      %User{}
lain's avatar
lain committed
99
      |> cast(params, [:bio, :name, :ap_id, :nickname, :avatar])
100
      |> validate_required([:name, :ap_id])
lain's avatar
lain committed
101 102 103 104 105
      |> 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
106
      |> put_embed(:info, info_cng)
lain's avatar
lain committed
107

108
    if changes.valid? do
lain's avatar
lain committed
109
      case info_cng.changes[:source_data] do
lain's avatar
lain committed
110 111 112
        %{"followers" => followers} ->
          changes
          |> put_change(:follower_address, followers)
lain's avatar
lain committed
113

lain's avatar
lain committed
114 115
        _ ->
          followers = User.ap_followers(%User{nickname: changes.changes[:nickname]})
lain's avatar
lain committed
116

lain's avatar
lain committed
117 118 119
          changes
          |> put_change(:follower_address, followers)
      end
120 121 122
    else
      changes
    end
lain's avatar
lain committed
123 124
  end

lain's avatar
lain committed
125
  def update_changeset(struct, params \\ %{}) do
Thog's avatar
Thog committed
126
    struct
lain's avatar
lain committed
127
    |> cast(params, [:bio, :name, :avatar])
lain's avatar
lain committed
128
    |> unique_constraint(:nickname)
href's avatar
href committed
129
    |> validate_format(:nickname, local_nickname_regex())
lain's avatar
lain committed
130
    |> validate_length(:bio, max: 5000)
lain's avatar
lain committed
131 132 133
    |> validate_length(:name, min: 1, max: 100)
  end

lain's avatar
lain committed
134
  def upgrade_changeset(struct, params \\ %{}) do
135 136 137 138
    params =
      params
      |> Map.put(:last_refreshed_at, NaiveDateTime.utc_now())

lain's avatar
lain committed
139 140 141 142
    info_cng =
      struct.info
      |> User.Info.user_upgrade(params[:info])

lain's avatar
lain committed
143
    struct
lain's avatar
lain committed
144
    |> cast(params, [:bio, :name, :follower_address, :avatar, :last_refreshed_at])
lain's avatar
lain committed
145
    |> unique_constraint(:nickname)
href's avatar
href committed
146
    |> validate_format(:nickname, local_nickname_regex())
lain's avatar
lain committed
147 148
    |> validate_length(:bio, max: 5000)
    |> validate_length(:name, max: 100)
lain's avatar
lain committed
149
    |> put_embed(:info, info_cng)
lain's avatar
lain committed
150 151
  end

Roger Braun's avatar
Roger Braun committed
152
  def password_update_changeset(struct, params) do
lain's avatar
lain committed
153 154 155 156 157
    changeset =
      struct
      |> cast(params, [:password, :password_confirmation])
      |> validate_required([:password, :password_confirmation])
      |> validate_confirmation(:password)
Roger Braun's avatar
Roger Braun committed
158

159 160 161
    OAuth.Token.delete_user_tokens(struct)
    OAuth.Authorization.delete_user_authorizations(struct)

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

Roger Braun's avatar
Roger Braun committed
165 166 167 168 169 170 171 172
      changeset
      |> put_change(:password_hash, hashed)
    else
      changeset
    end
  end

  def reset_password(user, data) do
lain's avatar
lain committed
173
    update_and_set_cache(password_update_changeset(user, data))
Roger Braun's avatar
Roger Braun committed
174 175
  end

176 177 178 179 180 181 182 183
  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
184 185
    info_change = User.Info.confirmation_changeset(%User.Info{}, confirmation_status)

lain's avatar
lain committed
186 187 188 189 190 191 192
    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)
href's avatar
href committed
193
      |> validate_format(:nickname, local_nickname_regex())
lain's avatar
lain committed
194 195 196
      |> validate_format(:email, @email_regex)
      |> validate_length(:bio, max: 1000)
      |> validate_length(:name, min: 1, max: 100)
Ivan Tashkinov's avatar
Ivan Tashkinov committed
197
      |> put_change(:info, info_change)
lain's avatar
lain committed
198 199

    if changeset.valid? do
200
      hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
lain's avatar
lain committed
201 202
      ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
      followers = User.ap_followers(%User{nickname: changeset.changes[:nickname]})
lain's avatar
lain committed
203

lain's avatar
lain committed
204 205 206 207
      changeset
      |> put_change(:password_hash, hashed)
      |> put_change(:ap_id, ap_id)
      |> put_change(:following, [followers])
208
      |> put_change(:follower_address, followers)
lain's avatar
lain committed
209 210 211 212 213
    else
      changeset
    end
  end

214 215
  @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
216 217
    with {:ok, user} <- Repo.insert(changeset),
         {:ok, _} = try_send_confirmation_email(user) do
218 219 220 221
      {:ok, user}
    end
  end

222 223 224 225 226 227 228 229 230 231
  def try_send_confirmation_email(%User{} = user) do
    if user.info.confirmation_pending do
      user
      |> Pleroma.UserEmail.account_confirmation_email()
      |> Pleroma.Mailer.deliver()
    else
      {:ok, :noop}
    end
  end

232 233 234 235 236 237 238 239 240 241
  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
242
  def maybe_direct_follow(%User{} = follower, %User{local: true, info: %{locked: true}}) do
243 244 245 246 247 248 249 250
    {: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
251
    if not User.ap_enabled?(followed) do
252
      follow(follower, followed)
253 254 255 256 257
    else
      {:ok, follower}
    end
  end

Maksim's avatar
Maksim committed
258
  def maybe_follow(%User{} = follower, %User{info: _info} = followed) do
259 260
    if not following?(follower, followed) do
      follow(follower, followed)
261
    else
262
      {:ok, follower}
263 264 265
    end
  end

lain's avatar
lain committed
266
  def follow(%User{} = follower, %User{info: info} = followed) do
267 268
    user_config = Application.get_env(:pleroma, :user)
    deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked)
269

270
    ap_followers = followed.follower_address
271

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

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

279 280 281 282 283 284 285 286
      true ->
        if !followed.local && follower.local && !ap_enabled?(followed) do
          Websub.subscribe(follower, followed)
        end

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

288 289 290 291
        follower =
          follower
          |> follow_changeset(%{following: following})
          |> update_and_set_cache
292

293 294 295
        {:ok, _} = update_follower_count(followed)

        follower
296
    end
lain's avatar
lain committed
297
  end
lain's avatar
lain committed
298 299

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

302
    if following?(follower, followed) and follower.ap_id != followed.ap_id do
lain's avatar
lain committed
303 304 305
      following =
        follower.following
        |> List.delete(ap_followers)
lain's avatar
lain committed
306

lain's avatar
lain committed
307 308 309 310
      {:ok, follower} =
        follower
        |> follow_changeset(%{following: following})
        |> update_and_set_cache
311 312 313 314

      {:ok, followed} = update_follower_count(followed)

      {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
315
    else
316
      {:error, "Not subscribed!"}
317
    end
lain's avatar
lain committed
318
  end
319

Maksim's avatar
Maksim committed
320
  @spec following?(User.t(), User.t()) :: boolean
321
  def following?(%User{} = follower, %User{} = followed) do
322
    Enum.member?(follower.following, followed.follower_address)
323
  end
lain's avatar
lain committed
324

325
  def locked?(%User{} = user) do
326
    user.info.locked || false
327 328
  end

lain's avatar
lain committed
329 330 331 332
  def get_by_ap_id(ap_id) do
    Repo.get_by(User, ap_id: ap_id)
  end

lain's avatar
lain committed
333 334
  def update_and_set_cache(changeset) do
    with {:ok, user} <- Repo.update(changeset) do
Thog's avatar
Thog committed
335 336 337
      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
338 339 340 341 342 343
      {:ok, user}
    else
      e -> e
    end
  end

lain's avatar
lain committed
344 345 346
  def invalidate_cache(user) do
    Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
    Cachex.del(:user_cache, "nickname:#{user.nickname}")
347
    Cachex.del(:user_cache, "user_info:#{user.id}")
lain's avatar
lain committed
348 349
  end

lain's avatar
lain committed
350
  def get_cached_by_ap_id(ap_id) do
351
    key = "ap_id:#{ap_id}"
Thog's avatar
Thog committed
352
    Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
lain's avatar
lain committed
353 354 355
  end

  def get_cached_by_nickname(nickname) do
356
    key = "nickname:#{nickname}"
Thog's avatar
Thog committed
357
    Cachex.fetch!(:user_cache, key, fn _ -> get_or_fetch_by_nickname(nickname) end)
lain's avatar
lain committed
358
  end
lain's avatar
lain committed
359

lain's avatar
lain committed
360
  def get_by_nickname(nickname) do
361 362 363
    Repo.get_by(User, nickname: nickname)
  end

364 365 366 367 368 369 370
  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
371 372
  def get_cached_user_info(user) do
    key = "user_info:#{user.id}"
Thog's avatar
Thog committed
373
    Cachex.fetch!(:user_cache, key, fn _ -> user_info(user) end)
lain's avatar
lain committed
374
  end
lain's avatar
lain committed
375

lain's avatar
lain committed
376 377 378 379 380 381 382 383 384
  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
385
  def get_or_fetch_by_nickname(nickname) do
lain's avatar
lain committed
386
    with %User{} = user <- get_by_nickname(nickname) do
lain's avatar
lain committed
387
      user
lain's avatar
lain committed
388 389 390 391 392 393 394 395
    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
396
    end
lain's avatar
lain committed
397
  end
lain's avatar
lain committed
398

399 400 401 402 403 404 405 406 407 408
  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
409 410 411 412

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

413 414 415 416 417 418 419 420 421 422
  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
423 424 425

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

427 428 429
  def get_follow_requests_query(%User{} = user) do
    from(
      a in Activity,
kaniini's avatar
kaniini committed
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
      where:
        fragment(
          "? ->> 'type' = 'Follow'",
          a.data
        ),
      where:
        fragment(
          "? ->> 'state' = 'pending'",
          a.data
        ),
      where:
        fragment(
          "? @> ?",
          a.data,
          ^%{"object" => user.ap_id}
        )
446 447 448 449 450 451 452 453
    )
  end

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

    users =
kaniini's avatar
kaniini committed
454 455 456
      Enum.map(reqs, fn req -> req.actor end)
      |> Enum.uniq()
      |> Enum.map(fn ap_id -> get_by_ap_id(ap_id) end)
457
      |> Enum.filter(fn u -> !following?(u, user) end)
458 459 460 461

    {:ok, users}
  end

462
  def increase_note_count(%User{} = user) do
lain's avatar
lain committed
463
    info_cng = User.Info.add_to_note_count(user.info, 1)
lain's avatar
lain committed
464 465 466 467

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

lain's avatar
lain committed
469
    update_and_set_cache(cng)
470 471
  end

472
  def decrease_note_count(%User{} = user) do
lain's avatar
lain committed
473
    info_cng = User.Info.add_to_note_count(user.info, -1)
lain's avatar
lain committed
474 475 476 477

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

lain's avatar
lain committed
479
    update_and_set_cache(cng)
480 481
  end

482
  def update_note_count(%User{} = user) do
lain's avatar
lain committed
483 484 485 486 487 488
    note_count_query =
      from(
        a in Object,
        where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
        select: count(a.id)
      )
489 490 491

    note_count = Repo.one(note_count_query)

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

lain's avatar
lain committed
494 495 496
    cng =
      change(user)
      |> put_embed(:info, info_cng)
497

lain's avatar
lain committed
498
    update_and_set_cache(cng)
499 500 501
  end

  def update_follower_count(%User{} = user) do
lain's avatar
lain committed
502 503 504 505 506 507 508
    follower_count_query =
      from(
        u in User,
        where: ^user.follower_address in u.following,
        where: u.id != ^user.id,
        select: count(u.id)
      )
509 510 511

    follower_count = Repo.one(follower_count_query)

lain's avatar
lain committed
512 513 514
    info_cng =
      user.info
      |> User.Info.set_follower_count(follower_count)
515

lain's avatar
lain committed
516 517 518
    cng =
      change(user)
      |> put_embed(:info, info_cng)
519

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

523
  def get_users_from_set_query(ap_ids, false) do
524 525
    from(
      u in User,
526
      where: u.ap_id in ^ap_ids
527 528 529
    )
  end

530 531
  def get_users_from_set_query(ap_ids, true) do
    query = get_users_from_set_query(ap_ids, false)
532 533 534

    from(
      u in query,
535 536 537 538
      where: u.local == true
    )
  end

539 540 541 542 543
  def get_users_from_set(ap_ids, local_only \\ true) do
    get_users_from_set_query(ap_ids, local_only)
    |> Repo.all()
  end

544
  def get_recipients_from_activity(%Activity{recipients: to}) do
lain's avatar
lain committed
545 546 547 548 549 550
    query =
      from(
        u in User,
        where: u.ap_id in ^to,
        or_where: fragment("? && ?", u.following, ^to)
      )
551

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

554 555 556
    Repo.all(query)
  end

557
  def search(query, resolve \\ false) do
558 559 560
    # strip the beginning @ off if there is a query
    query = String.trim_leading(query, "@")

lain's avatar
lain committed
561 562 563
    if resolve do
      User.get_or_fetch_by_nickname(query)
    end
lain's avatar
lain committed
564

lain's avatar
lain committed
565
    inner =
lain's avatar
lain committed
566 567
      from(
        u in User,
lain's avatar
lain committed
568
        select_merge: %{
kaniini's avatar
kaniini committed
569 570 571 572 573 574 575
          search_distance:
            fragment(
              "? <-> (? || ?)",
              ^query,
              u.nickname,
              u.name
            )
576 577
        },
        where: not is_nil(u.nickname)
lain's avatar
lain committed
578 579
      )

kaniini's avatar
kaniini committed
580 581 582 583 584 585
    q =
      from(
        s in subquery(inner),
        order_by: s.search_distance,
        limit: 20
      )
lain's avatar
lain committed
586

lain's avatar
lain committed
587 588
    Repo.all(q)
  end
lain's avatar
lain committed
589

590 591 592 593 594 595 596 597 598 599 600 601 602 603
  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
604 605 606
    info_cng =
      blocker.info
      |> User.Info.add_to_block(ap_id)
lain's avatar
lain committed
607

lain's avatar
lain committed
608 609 610 611 612
    cng =
      change(blocker)
      |> put_embed(:info, info_cng)

    update_and_set_cache(cng)
lain's avatar
lain committed
613 614
  end

615 616 617 618 619
  # 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
620 621 622 623
  def unblock(blocker, %{ap_id: ap_id}) do
    info_cng =
      blocker.info
      |> User.Info.remove_from_block(ap_id)
lain's avatar
lain committed
624

lain's avatar
lain committed
625 626 627 628 629
    cng =
      change(blocker)
      |> put_embed(:info, info_cng)

    update_and_set_cache(cng)
lain's avatar
lain committed
630 631 632
  end

  def blocks?(user, %{ap_id: ap_id}) do
lain's avatar
lain committed
633 634
    blocks = user.info.blocks
    domain_blocks = user.info.domain_blocks
eal's avatar
eal committed
635
    %{host: host} = URI.parse(ap_id)
eal's avatar
eal committed
636 637 638 639 640

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

  def block_domain(user, domain) do
lain's avatar
lain committed
644 645 646
    info_cng =
      user.info
      |> User.Info.add_to_domain_block(domain)
eal's avatar
eal committed
647

lain's avatar
lain committed
648 649 650
    cng =
      change(user)
      |> put_embed(:info, info_cng)
lain's avatar
lain committed
651 652

    update_and_set_cache(cng)
eal's avatar
eal committed
653 654 655
  end

  def unblock_domain(user, domain) do
lain's avatar
lain committed
656 657 658
    info_cng =
      user.info
      |> User.Info.remove_from_domain_block(domain)
eal's avatar
eal committed
659

lain's avatar
lain committed
660 661 662
    cng =
      change(user)
      |> put_embed(:info, info_cng)
lain's avatar
lain committed
663 664

    update_and_set_cache(cng)
lain's avatar
lain committed
665 666
  end

lain's avatar
lain committed
667
  def local_user_query() do
668 669 670 671 672
    from(
      u in User,
      where: u.local == true,
      where: not is_nil(u.nickname)
    )
lain's avatar
lain committed
673 674
  end

kaniini's avatar
kaniini committed
675 676 677 678 679 680 681 682
  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
683
  def deactivate(%User{} = user, status \\ true) do
lain's avatar
lain committed
684
    info_cng = User.Info.set_activation_status(user.info, status)
lain's avatar
lain committed
685 686 687 688

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

    update_and_set_cache(cng)
lain's avatar
lain committed
691
  end
lain's avatar
lain committed
692

lain's avatar
lain committed
693
  def delete(%User{} = user) do
lain's avatar
lain committed
694 695 696
    {:ok, user} = User.deactivate(user)

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

lain's avatar
lain committed
699
    followers
lain's avatar
lain committed
700
    |> Enum.each(fn follower -> User.unfollow(follower, user) end)
lain's avatar
lain committed
701 702

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

lain's avatar
lain committed
704
    friends
lain's avatar
lain committed
705
    |> Enum.each(fn followed -> User.unfollow(user, followed) end)
lain's avatar
lain committed
706

lain's avatar
lain committed
707
    query = from(a in Activity, where: a.actor == ^user.ap_id)
lain's avatar
lain committed
708 709

    Repo.all(query)
lain's avatar
lain committed
710
    |> Enum.each(fn activity ->
lain's avatar
lain committed
711
      case activity.data["type"] do
lain's avatar
lain committed
712
        "Create" ->
713
          ActivityPub.delete(Object.normalize(activity.data["object"]))
lain's avatar
lain committed
714 715 716 717

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

721
    {:ok, user}
lain's avatar
lain committed
722
  end
723

lain's avatar
lain committed
724
  def html_filter_policy(%User{info: %{no_rich_text: true}}) do
kaniini's avatar
kaniini committed
725 726 727 728 729
    Pleroma.HTML.Scrubber.TwitterText
  end

  def html_filter_policy(_), do: nil

730
  def get_or_fetch_by_ap_id(ap_id) do
731 732 733
    user = get_by_ap_id(ap_id)

    if !is_nil(user) and !User.needs_update?(user) do
734 735
      user
    else
lain's avatar
lain committed
736 737 738
      ap_try = ActivityPub.make_user_from_ap_id(ap_id)

      case ap_try do
lain's avatar
lain committed
739 740 741
        {:ok, user} ->
          user

lain's avatar
lain committed
742 743 744
        _ ->
          case OStatus.make_user(ap_id) do
            {:ok, user} -> user
feld's avatar
feld committed
745
            _ -> {:error, "Could not fetch by AP id"}
lain's avatar
lain committed
746
          end
747 748 749 750
      end
    end
  end

751
  def get_or_create_instance_user do
752 753 754
    relay_uri = "#{Pleroma.Web.Endpoint.url()}/relay"

    if user = get_by_ap_id(relay_uri) do
755 756 757
      user
    else
      changes =
lain's avatar
lain committed
758
        %User{info: %User.Info{}}
759
        |> cast(%{}, [:ap_id, :nickname, :local])
760
        |> put_change(:ap_id, relay_uri)
761 762
        |> put_change(:nickname, nil)
        |> put_change(:local, true)
763
        |> put_change(:follower_address, relay_uri <> "/followers")
764 765 766 767 768 769

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

770
  # AP style
lain's avatar
lain committed
771
  def public_key_from_info(%{
lain's avatar
lain committed
772
        source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}
lain's avatar
lain committed
773 774
      }) do
    key =
Maksim's avatar
Maksim committed
775 776
      public_key_pem
      |> :public_key.pem_decode()
lain's avatar
lain committed
777 778
      |> hd()
      |> :public_key.pem_entry_decode()
779

lain's avatar
lain committed
780
    {:ok, key}
781 782 783
  end

  # OStatus Magic Key
lain's avatar
lain committed
784
  def public_key_from_info(%{magic_key: magic_key}) do
785 786 787
    {:ok, Pleroma.Web.Salmon.decode_key(magic_key)}
  end

788
  def get_public_key_for_ap_id(ap_id) do
789 790
    with %User{} = user <- get_or_fetch_by_ap_id(ap_id),
         {:ok, public_key} <- public_key_from_info(user.info) do
791 792 793 794 795
      {:ok, public_key}
    else
      _ -> :error
    end
  end
lain's avatar
lain committed
796

797 798 799
  defp blank?(""), do: nil
  defp blank?(n), do: n

lain's avatar
lain committed
800
  def insert_or_update_user(data) do
lain's avatar
lain committed
801 802 803 804
    data =
      data
      |> Map.put(:name, blank?(data[:name]) || data[:nickname])

lain's avatar
lain committed
805
    cs = User.remote_user_creation(data)
lain's avatar
lain committed
806

lain's avatar
lain committed
807 808
    Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname)
  end
809

810
  def ap_enabled?(%User{local: true}), do: true
lain's avatar
lain committed
811
  def ap_enabled?(%User{info: info}), do: info.ap_enabled
lain's avatar
lain committed
812
  def ap_enabled?(_), do: false
lain's avatar
lain committed
813

Maksim's avatar
Maksim committed
814 815 816 817
  @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)
818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841

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

843
  def parse_bio(bio, user \\ %User{info: %{source_data: %{}}})
lain's avatar
lain committed
844 845
  def parse_bio(nil, _user), do: ""
  def parse_bio(bio, _user) when bio == "", do: bio
846 847

  def parse_bio(bio, user) do
Maxim Filippov's avatar
Maxim Filippov committed
848 849 850 851 852 853 854 855 856 857
    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
858 859 860
    bio
    |> CommonUtils.format_input(mentions, tags, "text/plain")
    |> Formatter.emojify(emoji)
Maxim Filippov's avatar
Maxim Filippov committed
861
  end
862

863 864 865 866 867
  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
868

Maksim's avatar
Maksim committed
869 870 871 872
  def tag(nickname, tags) when is_binary(nickname),
    do: tag(User.get_by_nickname(nickname), tags)

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

875 876 877 878 879
  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
880

881 882
  def untag(nickname, tags) when is_binary(nickname),
    do: untag(User.get_by_nickname(nickname), tags)
883

884 885
  def untag(%User{} = user, tags),
    do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
886

887 888 889 890 891
  defp update_tags(%User{} = user, new_tags) do
    {:ok, updated_user} =
      user
      |> change(%{tags: new_tags})
      |> Repo.update()
892

893
    updated_user
894
  end
Ivan Tashkinov's avatar
Ivan Tashkinov committed
895

896 897 898 899 900
  defp normalize_tags(tags) do
    [tags]
    |> List.flatten()
    |> Enum.map(&String.downcase(&1))
  end
href's avatar
href committed
901 902 903 904