activity_pub.ex 30.7 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
defmodule Pleroma.Web.ActivityPub.ActivityPub do
Haelwenn's avatar
Haelwenn committed
6
  alias Pleroma.Activity
7
  alias Pleroma.Conversation
8 9
  alias Pleroma.Instances
  alias Pleroma.Notification
Haelwenn's avatar
Haelwenn committed
10
  alias Pleroma.Object
11
  alias Pleroma.Object.Fetcher
12
  alias Pleroma.Pagination
13
  alias Pleroma.Repo
Haelwenn's avatar
Haelwenn committed
14 15 16
  alias Pleroma.Upload
  alias Pleroma.User
  alias Pleroma.Web.ActivityPub.MRF
17
  alias Pleroma.Web.ActivityPub.Transmogrifier
Haelwenn's avatar
Haelwenn committed
18
  alias Pleroma.Web.Federator
19
  alias Pleroma.Web.WebFinger
20

lain's avatar
lain committed
21
  import Ecto.Query
lain's avatar
lain committed
22
  import Pleroma.Web.ActivityPub.Utils
lain's avatar
lain committed
23
  import Pleroma.Web.ActivityPub.Visibility
24

lain's avatar
lain committed
25
  require Logger
lain's avatar
lain committed
26

lain's avatar
lain committed
27 28
  @httpoison Application.get_env(:pleroma, :httpoison)

29 30
  # For Announce activities, we filter the recipients based on following status for any actors
  # that match actual users.  See issue #164 for more information about why this is necessary.
31 32 33
  defp get_recipients(%{"type" => "Announce"} = data) do
    to = data["to"] || []
    cc = data["cc"] || []
34 35
    actor = User.get_cached_by_ap_id(data["actor"])

36 37 38 39 40 41 42 43 44 45 46
    recipients =
      (to ++ cc)
      |> Enum.filter(fn recipient ->
        case User.get_cached_by_ap_id(recipient) do
          nil ->
            true

          user ->
            User.following?(user, actor)
        end
      end)
47 48

    {recipients, to, cc}
49 50
  end

Maxim Filippov's avatar
Maxim Filippov committed
51 52 53 54 55 56 57 58
  defp get_recipients(%{"type" => "Create"} = data) do
    to = data["to"] || []
    cc = data["cc"] || []
    actor = data["actor"] || []
    recipients = (to ++ cc ++ [actor]) |> Enum.uniq()
    {recipients, to, cc}
  end

59 60 61 62 63
  defp get_recipients(data) do
    to = data["to"] || []
    cc = data["cc"] || []
    recipients = to ++ cc
    {recipients, to, cc}
lain's avatar
lain committed
64 65
  end

66
  defp check_actor_is_active(actor) do
67 68
    if not is_nil(actor) do
      with user <- User.get_cached_by_ap_id(actor),
lain's avatar
lain committed
69
           false <- user.info.deactivated do
70 71 72 73
        :ok
      else
        _e -> :reject
      end
74 75 76 77 78
    else
      :ok
    end
  end

79
  defp check_remote_limit(%{"object" => %{"content" => content}}) when not is_nil(content) do
80 81 82 83 84 85
    limit = Pleroma.Config.get([:instance, :remote_limit])
    String.length(content) <= limit
  end

  defp check_remote_limit(_), do: true

86 87 88 89 90 91 92 93
  def increase_note_count_if_public(actor, object) do
    if is_public?(object), do: User.increase_note_count(actor), else: {:ok, actor}
  end

  def decrease_note_count_if_public(actor, object) do
    if is_public?(object), do: User.decrease_note_count(actor), else: {:ok, actor}
  end

94
  def increase_replies_count_if_reply(%{
rinpatch's avatar
rinpatch committed
95
        "object" => %{"inReplyTo" => reply_ap_id} = object,
96 97 98 99 100 101 102 103 104 105
        "type" => "Create"
      }) do
    if is_public?(object) do
      Object.increase_replies_count(reply_ap_id)
    end
  end

  def increase_replies_count_if_reply(_create_data), do: :noop

  def decrease_replies_count_if_reply(%Object{
rinpatch's avatar
rinpatch committed
106
        data: %{"inReplyTo" => reply_ap_id} = object
107 108 109 110 111 112 113 114
      }) do
    if is_public?(object) do
      Object.decrease_replies_count(reply_ap_id)
    end
  end

  def decrease_replies_count_if_reply(_object), do: :noop

rinpatch's avatar
rinpatch committed
115
  def insert(map, local \\ true, fake \\ false) when is_map(map) do
116
    with nil <- Activity.normalize(map),
117
         map <- lazy_put_activity_defaults(map, fake),
118
         :ok <- check_actor_is_active(map["actor"]),
119
         {_, true} <- {:remote_limit_error, check_remote_limit(map)},
href's avatar
href committed
120
         {:ok, map} <- MRF.filter(map),
rinpatch's avatar
rinpatch committed
121 122
         {recipients, _, _} = get_recipients(map),
         {:fake, false, map, recipients} <- {:fake, fake, map, recipients},
123
         {:ok, map, object} <- insert_full_object(map) do
lain's avatar
lain committed
124 125 126 127 128
      {:ok, activity} =
        Repo.insert(%Activity{
          data: map,
          local: local,
          actor: map["actor"],
129
          recipients: recipients
lain's avatar
lain committed
130 131
        })

132 133 134 135 136 137 138 139
      # Splice in the child object if we have one.
      activity =
        if !is_nil(object) do
          Map.put(activity, :object, object)
        else
          activity
        end

140 141 142 143
      Task.start(fn ->
        Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
      end)

144
      Notification.create_notifications(activity)
145 146 147 148 149 150

      participations =
        activity
        |> Conversation.create_or_bump_for()
        |> get_participations()

lain's avatar
lain committed
151
      stream_out(activity)
152
      stream_out_participations(participations)
153
      {:ok, activity}
lain's avatar
lain committed
154
    else
rinpatch's avatar
rinpatch committed
155 156 157 158
      %Activity{} = activity ->
        {:ok, activity}

      {:fake, true, map, recipients} ->
rinpatch's avatar
rinpatch committed
159 160 161 162 163 164 165 166
        activity = %Activity{
          data: map,
          local: local,
          actor: map["actor"],
          recipients: recipients,
          id: "pleroma:fakeid"
        }

rinpatch's avatar
oof  
rinpatch committed
167
        Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
rinpatch's avatar
rinpatch committed
168
        {:ok, activity}
rinpatch's avatar
rinpatch committed
169 170 171

      error ->
        {:error, error}
lain's avatar
lain committed
172
    end
lain's avatar
lain committed
173
  end
lain's avatar
lain committed
174

175 176 177 178 179 180 181 182 183 184 185 186 187
  defp get_participations({:ok, %{participations: participations}}), do: participations
  defp get_participations(_), do: []

  def stream_out_participations(participations) do
    participations =
      participations
      |> Repo.preload(:user)

    Enum.each(participations, fn participation ->
      Pleroma.Web.Streamer.stream("participation", participation)
    end)
  end

lain's avatar
lain committed
188
  def stream_out(activity) do
csaurus's avatar
csaurus committed
189 190
    public = "https://www.w3.org/ns/activitystreams#Public"

191
    if activity.data["type"] in ["Create", "Announce", "Delete"] do
lain's avatar
lain committed
192
      Pleroma.Web.Streamer.stream("user", activity)
eal's avatar
eal committed
193
      Pleroma.Web.Streamer.stream("list", activity)
lain's avatar
lain committed
194

csaurus's avatar
csaurus committed
195 196
      if Enum.member?(activity.data["to"], public) do
        Pleroma.Web.Streamer.stream("public", activity)
197

csaurus's avatar
csaurus committed
198 199 200
        if activity.local do
          Pleroma.Web.Streamer.stream("public:local", activity)
        end
201

202
        if activity.data["type"] in ["Create"] do
203 204
          object = Object.normalize(activity)

205
          object.data
206 207
          |> Map.get("tag", [])
          |> Enum.filter(fn tag -> is_bitstring(tag) end)
Haelwenn's avatar
Haelwenn committed
208
          |> Enum.each(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end)
209

210
          if object.data["attachment"] != [] do
211
            Pleroma.Web.Streamer.stream("public:media", activity)
212

213 214 215
            if activity.local do
              Pleroma.Web.Streamer.stream("public:local:media", activity)
            end
216 217
          end
        end
csaurus's avatar
csaurus committed
218
      else
219
        # TODO: Write test, replace with visibility test
csaurus's avatar
csaurus committed
220 221 222
        if !Enum.member?(activity.data["cc"] || [], public) &&
             !Enum.member?(
               activity.data["to"],
223
               User.get_cached_by_ap_id(activity.data["actor"]).follower_address
csaurus's avatar
csaurus committed
224 225
             ),
           do: Pleroma.Web.Streamer.stream("direct", activity)
lain's avatar
lain committed
226
      end
lain's avatar
lain committed
227 228 229
    end
  end

rinpatch's avatar
rinpatch committed
230
  def create(%{to: to, actor: actor, context: context, object: object} = params, fake \\ false) do
231
    additional = params[:additional] || %{}
lain's avatar
lain committed
232 233
    # only accept false as false value
    local = !(params[:local] == false)
234 235
    published = params[:published]

lain's avatar
lain committed
236 237 238 239 240
    with create_data <-
           make_create_data(
             %{to: to, actor: actor, published: published, context: context, object: object},
             additional
           ),
rinpatch's avatar
rinpatch committed
241 242
         {:ok, activity} <- insert(create_data, local, fake),
         {:fake, false, activity} <- {:fake, fake, activity},
243
         _ <- increase_replies_count_if_reply(create_data),
244 245
         # Changing note count prior to enqueuing federation task in order to avoid
         # race conditions on updating user.info
246
         {:ok, _actor} <- increase_note_count_if_public(actor, activity),
247
         :ok <- maybe_federate(activity) do
248
      {:ok, activity}
rinpatch's avatar
rinpatch committed
249 250 251
    else
      {:fake, true, activity} ->
        {:ok, activity}
252
    end
lain's avatar
lain committed
253
  end
lain's avatar
lain committed
254

255
  def accept(%{to: to, actor: actor, object: object} = params) do
lain's avatar
lain committed
256 257
    # only accept false as false value
    local = !(params[:local] == false)
258

259
    with data <- %{"to" => to, "type" => "Accept", "actor" => actor.ap_id, "object" => object},
260
         {:ok, activity} <- insert(data, local),
261
         :ok <- maybe_federate(activity) do
262 263 264 265
      {:ok, activity}
    end
  end

266 267 268 269
  def reject(%{to: to, actor: actor, object: object} = params) do
    # only accept false as false value
    local = !(params[:local] == false)

270
    with data <- %{"to" => to, "type" => "Reject", "actor" => actor.ap_id, "object" => object},
271
         {:ok, activity} <- insert(data, local),
272
         :ok <- maybe_federate(activity) do
273 274 275 276
      {:ok, activity}
    end
  end

lain's avatar
lain committed
277
  def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
lain's avatar
lain committed
278 279 280 281 282 283 284 285 286 287
    # only accept false as false value
    local = !(params[:local] == false)

    with data <- %{
           "to" => to,
           "cc" => cc,
           "type" => "Update",
           "actor" => actor,
           "object" => object
         },
lain's avatar
lain committed
288 289 290 291 292 293
         {:ok, activity} <- insert(data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
    end
  end

lain's avatar
lain committed
294
  # TODO: This is weird, maybe we shouldn't check here if we can make the activity.
lain's avatar
lain committed
295 296 297 298 299 300
  def like(
        %User{ap_id: ap_id} = user,
        %Object{data: %{"id" => _}} = object,
        activity_id \\ nil,
        local \\ true
      ) do
lain's avatar
lain committed
301 302 303 304 305 306 307 308 309
    with nil <- get_existing_like(ap_id, object),
         like_data <- make_like_data(user, object, activity_id),
         {:ok, activity} <- insert(like_data, local),
         {:ok, object} <- add_like_to_object(activity, object),
         :ok <- maybe_federate(activity) do
      {:ok, activity, object}
    else
      %Activity{} = activity -> {:ok, activity, object}
      error -> {:error, error}
lain's avatar
lain committed
310
    end
lain's avatar
lain committed
311 312
  end

313 314 315 316 317 318 319 320 321 322 323 324 325
  def unlike(
        %User{} = actor,
        %Object{} = object,
        activity_id \\ nil,
        local \\ true
      ) do
    with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object),
         unlike_data <- make_unlike_data(actor, like_activity, activity_id),
         {:ok, unlike_activity} <- insert(unlike_data, local),
         {:ok, _activity} <- Repo.delete(like_activity),
         {:ok, object} <- remove_like_from_object(like_activity, object),
         :ok <- maybe_federate(unlike_activity) do
      {:ok, unlike_activity, like_activity, object}
lain's avatar
lain committed
326 327
    else
      _e -> {:ok, object}
lain's avatar
lain committed
328 329 330
    end
  end

lain's avatar
lain committed
331 332 333 334
  def announce(
        %User{ap_id: _} = user,
        %Object{data: %{"id" => _}} = object,
        activity_id \\ nil,
335 336
        local \\ true,
        public \\ true
lain's avatar
lain committed
337
      ) do
lain's avatar
lain committed
338
    with true <- is_public?(object),
339
         announce_data <- make_announce_data(user, object, activity_id, public),
lain's avatar
lain committed
340 341 342 343 344 345 346
         {:ok, activity} <- insert(announce_data, local),
         {:ok, object} <- add_announce_to_object(activity, object),
         :ok <- maybe_federate(activity) do
      {:ok, activity, object}
    else
      error -> {:error, error}
    end
lain's avatar
lain committed
347 348
  end

349 350 351
  def unannounce(
        %User{} = actor,
        %Object{} = object,
352 353
        activity_id \\ nil,
        local \\ true
354
      ) do
normandy's avatar
normandy committed
355 356
    with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object),
         unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id),
357
         {:ok, unannounce_activity} <- insert(unannounce_data, local),
normandy's avatar
normandy committed
358 359 360
         :ok <- maybe_federate(unannounce_activity),
         {:ok, _activity} <- Repo.delete(announce_activity),
         {:ok, object} <- remove_announce_from_object(announce_activity, object) do
361
      {:ok, unannounce_activity, object}
normandy's avatar
normandy committed
362 363 364 365 366
    else
      _e -> {:ok, object}
    end
  end

lain's avatar
lain committed
367 368 369
  def follow(follower, followed, activity_id \\ nil, local \\ true) do
    with data <- make_follow_data(follower, followed, activity_id),
         {:ok, activity} <- insert(data, local),
370
         :ok <- maybe_federate(activity) do
lain's avatar
lain committed
371 372
      {:ok, activity}
    end
lain's avatar
lain committed
373 374
  end

normandy's avatar
normandy committed
375
  def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
lain's avatar
lain committed
376
    with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
377
         {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"),
normandy's avatar
normandy committed
378
         unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
lain's avatar
lain committed
379
         {:ok, activity} <- insert(unfollow_data, local),
380
         :ok <- maybe_federate(activity) do
lain's avatar
lain committed
381 382
      {:ok, activity}
    end
lain's avatar
lain committed
383 384
  end

lain's avatar
lain committed
385 386
  def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
    user = User.get_cached_by_ap_id(actor)
387
    to = (object.data["to"] || []) ++ (object.data["cc"] || [])
388

389 390 391 392 393 394 395 396
    with {:ok, object, activity} <- Object.delete(object),
         data <- %{
           "type" => "Delete",
           "actor" => actor,
           "object" => id,
           "to" => to,
           "deleted_activity_id" => activity && activity.id
         },
lain's avatar
lain committed
397
         {:ok, activity} <- insert(data, local),
398
         _ <- decrease_replies_count_if_reply(object),
399 400
         # Changing note count prior to enqueuing federation task in order to avoid
         # race conditions on updating user.info
401
         {:ok, _actor} <- decrease_note_count_if_public(user, object),
402
         :ok <- maybe_federate(activity) do
lain's avatar
lain committed
403 404 405 406
      {:ok, activity}
    end
  end

normandy's avatar
normandy committed
407
  def block(blocker, blocked, activity_id \\ nil, local \\ true) do
408 409 410
    ap_config = Application.get_env(:pleroma, :activitypub)
    unfollow_blocked = Keyword.get(ap_config, :unfollow_blocked)
    outgoing_blocks = Keyword.get(ap_config, :outgoing_blocks)
normandy's avatar
normandy committed
411

412
    with true <- unfollow_blocked do
413
      follow_activity = fetch_latest_follow(blocker, blocked)
squidboi's avatar
squidboi committed
414

415 416 417
      if follow_activity do
        unfollow(blocker, blocked, nil, local)
      end
normandy's avatar
normandy committed
418 419
    end

420 421
    with true <- outgoing_blocks,
         block_data <- make_block_data(blocker, blocked, activity_id),
normandy's avatar
normandy committed
422 423 424
         {:ok, activity} <- insert(block_data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
425
    else
squidboi's avatar
squidboi committed
426
      _e -> {:ok, nil}
normandy's avatar
normandy committed
427 428 429
    end
  end

normandy's avatar
normandy committed
430
  def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do
normandy's avatar
normandy committed
431
    with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked),
normandy's avatar
normandy committed
432
         unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id),
normandy's avatar
normandy committed
433 434 435 436 437 438
         {:ok, activity} <- insert(unblock_data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
    end
  end

minibikini's avatar
minibikini committed
439 440 441 442 443 444 445 446 447 448 449
  def flag(
        %{
          actor: actor,
          context: context,
          account: account,
          statuses: statuses,
          content: content
        } = params
      ) do
    # only accept false as false value
    local = !(params[:local] == false)
450 451 452
    forward = !(params[:forward] == false)

    additional = params[:additional] || %{}
minibikini's avatar
minibikini committed
453

454
    params = %{
minibikini's avatar
minibikini committed
455 456 457 458 459 460
      actor: actor,
      context: context,
      account: account,
      statuses: statuses,
      content: content
    }
461 462 463 464 465

    additional =
      if forward do
        Map.merge(additional, %{"to" => [], "cc" => [account.ap_id]})
      else
466
        Map.merge(additional, %{"to" => [], "cc" => []})
467 468 469 470 471
      end

    with flag_data <- make_flag_data(params, additional),
         {:ok, activity} <- insert(flag_data, local),
         :ok <- maybe_federate(activity) do
472 473
      Enum.each(User.all_superusers(), fn superuser ->
        superuser
474
        |> Pleroma.Emails.AdminEmail.report(actor, account, statuses, content)
475
        |> Pleroma.Emails.Mailer.deliver_async()
476 477
      end)

478 479
      {:ok, activity}
    end
minibikini's avatar
minibikini committed
480 481
  end

482
  defp fetch_activities_for_context_query(context, opts) do
lain's avatar
lain committed
483 484
    public = ["https://www.w3.org/ns/activitystreams#Public"]

lain's avatar
lain committed
485 486 487
    recipients =
      if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public

488 489 490 491 492 493 494 495 496 497 498
    from(activity in Activity)
    |> restrict_blocked(opts)
    |> restrict_recipients(recipients, opts["user"])
    |> where(
      [activity],
      fragment(
        "?->>'type' = ? and ?->>'context' = ?",
        activity.data,
        "Create",
        activity.data,
        ^context
lain's avatar
lain committed
499
      )
500 501 502 503 504 505 506 507 508 509 510
    )
    |> order_by([activity], desc: activity.id)
  end

  @spec fetch_activities_for_context(String.t(), keyword() | map()) :: [Activity.t()]
  def fetch_activities_for_context(context, opts \\ %{}) do
    context
    |> fetch_activities_for_context_query(opts)
    |> Activity.with_preloaded_object()
    |> Repo.all()
  end
lain's avatar
lain committed
511

512 513 514 515 516 517 518 519
  @spec fetch_latest_activity_id_for_context(String.t(), keyword() | map()) ::
          Pleroma.FlakeId.t() | nil
  def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
    context
    |> fetch_activities_for_context_query(opts)
    |> limit(1)
    |> select([a], a.id)
    |> Repo.one()
lain's avatar
lain committed
520 521
  end

522
  def fetch_public_activities(opts \\ %{}) do
lain's avatar
lain committed
523
    q = fetch_activities_query(["https://www.w3.org/ns/activitystreams#Public"], opts)
lain's avatar
lain committed
524

lain's avatar
lain committed
525
    q
526
    |> restrict_unlisted()
527
    |> Pagination.fetch_paginated(opts)
lain's avatar
lain committed
528
    |> Enum.reverse()
lain's avatar
lain committed
529 530
  end

531 532
  @valid_visibilities ~w[direct unlisted public private]

533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
  defp restrict_visibility(query, %{visibility: visibility})
       when is_list(visibility) do
    if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
      query =
        from(
          a in query,
          where:
            fragment(
              "activity_visibility(?, ?, ?) = ANY (?)",
              a.actor,
              a.recipients,
              a.data,
              ^visibility
            )
        )

      Ecto.Adapters.SQL.to_sql(:all, Repo, query)

      query
    else
      Logger.error("Could not restrict visibility to #{visibility}")
    end
  end

lain's avatar
lain committed
557 558 559 560 561 562 563 564 565 566
  defp restrict_visibility(query, %{visibility: visibility})
       when visibility in @valid_visibilities do
    query =
      from(
        a in query,
        where:
          fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
      )

    Ecto.Adapters.SQL.to_sql(:all, Repo, query)
567

lain's avatar
lain committed
568
    query
569 570 571 572 573 574 575 576 577
  end

  defp restrict_visibility(_query, %{visibility: visibility})
       when visibility not in @valid_visibilities do
    Logger.error("Could not restrict visibility to #{visibility}")
  end

  defp restrict_visibility(query, _visibility), do: query

578 579 580 581 582 583
  def fetch_user_activities(user, reading_user, params \\ %{}) do
    params =
      params
      |> Map.put("type", ["Create", "Announce"])
      |> Map.put("actor_id", user.ap_id)
      |> Map.put("whole_db", true)
minibikini's avatar
minibikini committed
584
      |> Map.put("pinned_activity_ids", user.info.pinned_activities)
585 586 587 588 589 590 591 592 593 594 595 596 597

    recipients =
      if reading_user do
        ["https://www.w3.org/ns/activitystreams#Public"] ++
          [reading_user.ap_id | reading_user.following]
      else
        ["https://www.w3.org/ns/activitystreams#Public"]
      end

    fetch_activities(recipients, params)
    |> Enum.reverse()
  end

598 599
  defp restrict_since(query, %{"since_id" => ""}), do: query

lain's avatar
lain committed
600
  defp restrict_since(query, %{"since_id" => since_id}) do
lain's avatar
lain committed
601
    from(activity in query, where: activity.id > ^since_id)
lain's avatar
lain committed
602
  end
lain's avatar
lain committed
603

lain's avatar
lain committed
604
  defp restrict_since(query, _), do: query
lain's avatar
lain committed
605

606 607 608 609
  defp restrict_tag_reject(_query, %{"tag_reject" => _tag_reject, "skip_preload" => true}) do
    raise "Can't use the child object without preloading!"
  end

610 611
  defp restrict_tag_reject(query, %{"tag_reject" => tag_reject})
       when is_list(tag_reject) and tag_reject != [] do
612
    from(
613 614
      [_activity, object] in query,
      where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject)
615 616 617
    )
  end

618 619
  defp restrict_tag_reject(query, _), do: query

620 621 622 623
  defp restrict_tag_all(_query, %{"tag_all" => _tag_all, "skip_preload" => true}) do
    raise "Can't use the child object without preloading!"
  end

624 625 626
  defp restrict_tag_all(query, %{"tag_all" => tag_all})
       when is_list(tag_all) and tag_all != [] do
    from(
627 628
      [_activity, object] in query,
      where: fragment("(?)->'tag' \\?& (?)", object.data, ^tag_all)
629 630 631 632 633
    )
  end

  defp restrict_tag_all(query, _), do: query

634 635 636 637
  defp restrict_tag(_query, %{"tag" => _tag, "skip_preload" => true}) do
    raise "Can't use the child object without preloading!"
  end

638 639
  defp restrict_tag(query, %{"tag" => tag}) when is_list(tag) do
    from(
640 641
      [_activity, object] in query,
      where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag)
642 643 644
    )
  end

645
  defp restrict_tag(query, %{"tag" => tag}) when is_binary(tag) do
lain's avatar
lain committed
646
    from(
647 648
      [_activity, object] in query,
      where: fragment("(?)->'tag' \\? (?)", object.data, ^tag)
lain's avatar
lain committed
649
    )
Roger Braun's avatar
Roger Braun committed
650
  end
lain's avatar
lain committed
651

Roger Braun's avatar
Roger Braun committed
652 653
  defp restrict_tag(query, _), do: query

654 655 656 657 658
  defp restrict_to_cc(query, recipients_to, recipients_cc) do
    from(
      activity in query,
      where:
        fragment(
659 660
          "(?->'to' \\?| ?) or (?->'cc' \\?| ?)",
          activity.data,
661
          ^recipients_to,
662 663
          activity.data,
          ^recipients_cc
664 665 666 667
        )
    )
  end

feld's avatar
feld committed
668
  defp restrict_recipients(query, [], _user), do: query
lain's avatar
lain committed
669

670
  defp restrict_recipients(query, recipients, nil) do
lain's avatar
lain committed
671
    from(activity in query, where: fragment("? && ?", ^recipients, activity.recipients))
lain's avatar
lain committed
672
  end
lain's avatar
lain committed
673

674
  defp restrict_recipients(query, recipients, user) do
lain's avatar
lain committed
675 676
    from(
      activity in query,
677 678
      where: fragment("? && ?", ^recipients, activity.recipients),
      or_where: activity.actor == ^user.ap_id
lain's avatar
lain committed
679
    )
680
  end
681

lain's avatar
lain committed
682
  defp restrict_local(query, %{"local_only" => true}) do
lain's avatar
lain committed
683
    from(activity in query, where: activity.local == true)
lain's avatar
lain committed
684
  end
lain's avatar
lain committed
685

lain's avatar
lain committed
686
  defp restrict_local(query, _), do: query
lain's avatar
lain committed
687

lain's avatar
lain committed
688
  defp restrict_actor(query, %{"actor_id" => actor_id}) do
lain's avatar
lain committed
689
    from(activity in query, where: activity.actor == ^actor_id)
690
  end
lain's avatar
lain committed
691

lain's avatar
lain committed
692
  defp restrict_actor(query, _), do: query
693

lain's avatar
lain committed
694
  defp restrict_type(query, %{"type" => type}) when is_binary(type) do
695
    from(activity in query, where: fragment("?->>'type' = ?", activity.data, ^type))
lain's avatar
lain committed
696
  end
lain's avatar
lain committed
697

698
  defp restrict_type(query, %{"type" => type}) do
lain's avatar
lain committed
699
    from(activity in query, where: fragment("?->>'type' = ANY(?)", activity.data, ^type))
700
  end
lain's avatar
lain committed
701

702 703
  defp restrict_type(query, _), do: query

704
  defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
lain's avatar
lain committed
705 706
    from(
      activity in query,
707
      where: fragment(~s(? <@ (? #> '{"object","likes"}'\)), ^ap_id, activity.data)
lain's avatar
lain committed
708
    )
709
  end
lain's avatar
lain committed
710

711 712
  defp restrict_favorited_by(query, _), do: query

713 714 715 716
  defp restrict_media(_query, %{"only_media" => _val, "skip_preload" => true}) do
    raise "Can't use the child object without preloading!"
  end

eal's avatar
eal committed
717
  defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
lain's avatar
lain committed
718
    from(
719 720
      [_activity, object] in query,
      where: fragment("not (?)->'attachment' = (?)", object.data, ^[])
lain's avatar
lain committed
721
    )
eal's avatar
eal committed
722
  end
lain's avatar
lain committed
723

eal's avatar
eal committed
724 725
  defp restrict_media(query, _), do: query

726 727 728 729 730 731 732 733 734
  defp restrict_replies(query, %{"exclude_replies" => val}) when val == "true" or val == "1" do
    from(
      activity in query,
      where: fragment("?->'object'->>'inReplyTo' is null", activity.data)
    )
  end

  defp restrict_replies(query, _), do: query

735 736 737 738 739 740
  defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val == "true" or val == "1" do
    from(activity in query, where: fragment("?->>'type' != 'Announce'", activity.data))
  end

  defp restrict_reblogs(query, _), do: query

lain's avatar
lain committed
741 742
  defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query

743
  defp restrict_muted(query, %{"muting_user" => %User{info: info}}) do
744
    mutes = info.mutes
745 746 747 748 749 750 751 752 753 754

    from(
      activity in query,
      where: fragment("not (? = ANY(?))", activity.actor, ^mutes),
      where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes)
    )
  end

  defp restrict_muted(query, _), do: query

lain's avatar
lain committed
755
  defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
lain's avatar
lain committed
756 757
    blocks = info.blocks || []
    domain_blocks = info.domain_blocks || []
lain's avatar
lain committed
758 759 760

    from(
      activity in query,
761
      where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
762
      where: fragment("not (? && ?)", activity.recipients, ^blocks),
763 764 765 766 767 768 769
      where:
        fragment(
          "not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
          activity.data,
          activity.data,
          ^blocks
        ),
eal's avatar
eal committed
770
      where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks)
771
    )
772
  end
lain's avatar
lain committed
773

774 775
  defp restrict_blocked(query, _), do: query

776 777 778
  defp restrict_unlisted(query) do
    from(
      activity in query,
lain's avatar
lain committed
779 780
      where:
        fragment(
lain's avatar
lain committed
781
          "not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)",
lain's avatar
lain committed
782 783 784
          activity.data,
          ^["https://www.w3.org/ns/activitystreams#Public"]
        )
785 786 787
    )
  end

minibikini's avatar
minibikini committed
788 789 790 791 792 793
  defp restrict_pinned(query, %{"pinned" => "true", "pinned_activity_ids" => ids}) do
    from(activity in query, where: activity.id in ^ids)
  end

  defp restrict_pinned(query, _), do: query

Karen Konou's avatar
Karen Konou committed
794 795 796 797 798
  defp restrict_muted_reblogs(query, %{"muting_user" => %User{info: info}}) do
    muted_reblogs = info.muted_reblogs || []

    from(
      activity in query,
799 800 801 802 803 804 805
      where:
        fragment(
          "not ( ?->>'type' = 'Announce' and ? = ANY(?))",
          activity.data,
          activity.actor,
          ^muted_reblogs
        )
Karen Konou's avatar
Karen Konou committed
806 807 808 809 810
    )
  end

  defp restrict_muted_reblogs(query, _), do: query

811 812 813 814 815 816 817
  defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query

  defp maybe_preload_objects(query, _) do
    query
    |> Activity.with_preloaded_object()
  end

818 819 820 821 822 823 824
  defp maybe_preload_bookmarks(query, %{"skip_preload" => true}), do: query

  defp maybe_preload_bookmarks(query, opts) do
    query
    |> Activity.with_preloaded_bookmark(opts["user"])
  end

825 826 827 828 829 830 831 832 833 834 835 836
  defp maybe_order(query, %{order: :desc}) do
    query
    |> order_by(desc: :id)
  end

  defp maybe_order(query, %{order: :asc}) do
    query
    |> order_by(asc: :id)
  end

  defp maybe_order(query, _), do: query

lain's avatar
lain committed
837
  def fetch_activities_query(recipients, opts \\ %{}) do
838
    base_query = from(activity in Activity)
lain's avatar
lain committed
839

lain's avatar
lain committed
840
    base_query
841
    |> maybe_preload_objects(opts)
842
    |> maybe_preload_bookmarks(opts)
843
    |> maybe_order(opts)
844
    |> restrict_recipients(recipients, opts["user"])
Roger Braun's avatar
Roger Braun committed
845
    |> restrict_tag(opts)
846 847
    |> restrict_tag_reject(opts)
    |> restrict_tag_all(opts)
lain's avatar
lain committed
848 849 850
    |> restrict_since(opts)
    |> restrict_local(opts)
    |> restrict_actor(opts)
851
    |> restrict_type(opts)
852
    |> restrict_favorited_by(opts)
853
    |> restrict_blocked(opts)
854
    |> restrict_muted(opts)
eal's avatar
eal committed
855
    |> restrict_media(opts)
856
    |> restrict_visibility(opts)
857
    |> restrict_replies(opts)
858
    |> restrict_reblogs(opts)
minibikini's avatar
minibikini committed
859
    |> restrict_pinned(opts)
Karen Konou's avatar
Karen Konou committed
860
    |> restrict_muted_reblogs(opts)
lain's avatar
lain committed
861 862 863 864
  end

  def fetch_activities(recipients, opts \\ %{}) do
    fetch_activities_query(recipients, opts)
865
    |> Pagination.fetch_paginated(opts)
lain's avatar
lain committed
866
    |> Enum.reverse()
867 868
  end

869 870 871
  def fetch_activities_bounded(recipients_to, recipients_cc, opts \\ %{}) do
    fetch_activities_query([], opts)
    |> restrict_to_cc(recipients_to, recipients_cc)
872
    |> Pagination.fetch_paginated(opts)
873 874 875
    |> Enum.reverse()
  end

href's avatar
href committed
876 877
  def upload(file, opts \\ []) do
    with {:ok, data} <- Upload.store(file, opts) do
878 879 880 881 882 883 884
      obj_data =
        if opts[:actor] do
          Map.put(data, "actor", opts[:actor])
        else
          data
        end

885
      Repo.insert(%Object{data: obj_data})
886
    end
lain's avatar
lain committed
887
  end
lain's avatar
lain committed
888

lain's avatar
lain committed
889
  def user_data_from_user_object(data) do
lain's avatar
lain committed
890 891 892 893 894 895 896 897 898 899 900 901 902
    avatar =
      data["icon"]["url"] &&
        %{
          "type" => "Image",
          "url" => [%{"href" => data["icon"]["url"]}]
        }

    banner =
      data["image"]["url"] &&
        %{
          "type" => "Image",
          "url" => [%{"href" => data["image"]["url"]}]
        }
lain's avatar
lain committed
903

904
    locked = data["manuallyApprovesFollowers"] || false
905 906
    data = Transmogrifier.maybe_fix_user_object(data)

lain's avatar
lain committed
907 908 909 910 911
    user_data = %{
      ap_id: data["id"],
      info: %{
        "ap_enabled" => true,
        "source_data" => data,
912 913
        "banner" => banner,
        "locked" => locked
lain's avatar
lain committed
914 915 916 917 918 919 920
      },
      avatar: avatar,
      name: data["name"],
      follower_address: data["followers"],
      bio: data["summary"]
    }

921 922 923
    # nickname can be nil because of virtual actors
    user_data =
      if data["preferredUsername"] do
kaniini's avatar
kaniini committed
924 925 926 927 928
        Map.put(
          user_data,
          :nickname,
          "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}"
        )
929 930 931 932
      else
        Map.put(user_data, :nickname, nil)
      end

lain's avatar
lain committed
933 934 935
    {:ok, user_data}
  end

lain's avatar
lain committed
936
  def fetch_and_prepare_user_from_ap_id(ap_id) do
937
    with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do
lain's avatar
lain committed
938
      user_data_from_user_object(data)
lain's avatar
lain committed
939
    else
940
      e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
lain's avatar
lain committed
941 942 943 944
    end
  end

  def make_user_from_ap_id(ap_id) do
945
    if _user = User.get_cached_by_ap_id(ap_id) do
lain's avatar
lain committed
946 947 948 949
      Transmogrifier.upgrade_user_from_ap_id(ap_id)
    else
      with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
        User.insert_or_update_user(data)
950
      else
lain's avatar
lain committed
951
        e -> {:error, e}
952
      end
lain's avatar
lain committed
953 954
    end
  end
955

lain's avatar
lain committed
956 957 958
  def make_user_from_nickname(nickname) do
    with {:ok, %{"ap_id" => ap_id}} when not is_nil(ap_id) <- WebFinger.finger(nickname) do
      make_user_from_ap_id(ap_id)
lain's avatar
lain committed
959
    else
feld's avatar
feld committed
960
      _e -> {:error, "No AP id in WebFinger"}
lain's avatar
lain committed
961 962 963
    end
  end

964 965 966 967 968
  def should_federate?(inbox, public) do
    if public do
      true
    else
      inbox_info = URI.parse(inbox)
href's avatar
href committed
969
      !Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
970 971 972
    end
  end

973
  def publish(actor, activity) do
974
    remote_followers =
lain's avatar
lain committed
975 976 977 978 979 980
      if actor.follower_address in activity.recipients do
        {:ok, followers} = User.get_followers(actor)
        followers |> Enum.filter(&(!&1.local))
      else
        []
      end
981

982 983
    public = is_public?(activity)

984
    {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
lain's avatar
lain committed
985
    json = Jason.encode!(data)
lain's avatar
lain committed
986

987
    (Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers)
minibikini's avatar
minibikini committed
988 989 990 991 992 993
    |> Enum.filter(fn user -> User.ap_enabled?(user) end)
    |> Enum.map(fn %{info: %{source_data: data}} ->
      (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
    end)
    |> Enum.uniq()
    |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
994 995
    |> Instances.filter_reachable()
    |> Enum.each(fn {inbox, unreachable_since} ->
minibikini's avatar
minibikini committed
996
      Federator.publish_single_ap(%{
lain's avatar
lain committed
997 998 999
        inbox: inbox,
        json: json,
        actor: actor,
1000 1001
        id: activity.data["id"],
        unreachable_since: unreachable_since
lain's avatar
lain committed
1002 1003
      })
    end)
1004
  end
1005

1006
  def publish_one(%{inbox: inbox, json: json, actor: actor, id: id} = params) do
1007 1008
    Logger.info("Federating #{id} to #{inbox}")
    host = URI.parse(inbox).host
lain's avatar
lain committed
1009

1010 1011
    digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())

kaniini's avatar
kaniini committed
1012 1013
    date =
      NaiveDateTime.utc_now()
1014
      |> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
kaniini's avatar
kaniini committed
1015

lain's avatar
lain committed
1016
    signature =
1017 1018 1019
      Pleroma.Web.HTTPSignatures.sign(actor, %{
        host: host,
        "content-length": byte_size(json),
kaniini's avatar
kaniini committed
1020 1021
        digest: digest,
        date: date
1022
      })
lain's avatar
lain committed
1023

1024
    with {:ok, %{status: code}} when code in 200..299 <-
1025 1026 1027 1028 1029 1030
           result =
             @httpoison.post(
               inbox,
               json,
               [
                 {"Content-Type", "application/activity+json"},
kaniini's avatar
kaniini committed
1031
                 {"Date", date},
1032
                 {"signature", signature},
1033
                 {"digest", digest}
1034 1035
               ]
             ) do
1036 1037 1038
      if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
        do: Instances.set_reachable(inbox)

1039 1040
      result
    else
1041
      {_post_result, response} ->
1042
        unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
1043
        {:error, response}
1044
    end
1045 1046
  end

1047 1048 1049 1050 1051 1052 1053
  # filter out broken threads
  def contain_broken_threads(%Activity{} = activity, %User{} = user) do
    entire_thread_visible_for_user?(activity, user)
  end

  # do post-processing on a specific activity
  def contain_activity(%Activity{} = activity, %User{} = user) do
Karen Konou's avatar
Karen Konou committed
1054
    contain_broken_threads(activity, user)
1055 1056 1057 1058 1059 1060