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
  def auth_active?(user), do: user.info && !user.info.confirmation_pending

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

175 176 177 178 179 180 181 182
  def register_changeset(struct, params \\ %{}, opts \\ []) do
    confirmation_status =
      if opts[:confirmed] || !Pleroma.Config.get([:instance, :account_activation_required]) do
        :confirmed
      else
        :unconfirmed
      end

lain's avatar
lain committed
183 184 185 186 187 188 189
    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
190
      |> validate_format(:nickname, local_nickname_regex())
lain's avatar
lain committed
191 192 193
      |> validate_format(:email, @email_regex)
      |> validate_length(:bio, max: 1000)
      |> validate_length(:name, min: 1, max: 100)
194
      |> put_change(:info, User.Info.confirmation_changeset(%User.Info{}, confirmation_status))
lain's avatar
lain committed
195 196

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

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

211 212 213
  @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
  def register(%Ecto.Changeset{} = changeset) do
    with {:ok, user} <- Repo.insert(changeset) do
214
      {:ok, _} = try_send_confirmation_email(user)
215 216 217 218
      {:ok, user}
    end
  end

219 220 221 222 223 224 225 226 227 228
  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

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

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

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

267
    ap_followers = followed.follower_address
268

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

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

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

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

285 286 287 288
        follower =
          follower
          |> follow_changeset(%{following: following})
          |> update_and_set_cache
289

290 291 292
        {:ok, _} = update_follower_count(followed)

        follower
293
    end
lain's avatar
lain committed
294
  end
lain's avatar
lain committed
295 296

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

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

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

      {:ok, followed} = update_follower_count(followed)

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

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

322
  def locked?(%User{} = user) do
323
    user.info.locked || false
324 325
  end

lain's avatar
lain committed
326 327 328 329
  def get_by_ap_id(ap_id) do
    Repo.get_by(User, ap_id: ap_id)
  end

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

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

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

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

lain's avatar
lain committed
357
  def get_by_nickname(nickname) do
358 359 360
    Repo.get_by(User, nickname: nickname)
  end

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

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

396 397 398 399
  def get_by_confirmation_token(token) do
    Repo.one(from(u in User, where: fragment("? ->> 'confirmation_token' = ?", u.info, ^token)))
  end

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

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

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

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

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

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

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

    {:ok, users}
  end

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

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

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

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

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

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

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

    note_count = Repo.one(note_count_query)

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

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

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

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

    follower_count = Repo.one(follower_count_query)

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

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

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

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

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

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

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

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

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

555 556 557
    Repo.all(query)
  end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  def html_filter_policy(_), do: nil

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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