activity_pub.ex 24.5 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
Thog's avatar
Thog committed
6
  alias Pleroma.{Activity, Repo, Object, Upload, User, Notification}
href's avatar
href committed
7
  alias Pleroma.Web.ActivityPub.{Transmogrifier, MRF}
lain's avatar
lain committed
8
  alias Pleroma.Web.WebFinger
lain's avatar
lain committed
9
  alias Pleroma.Web.Federator
10
  alias Pleroma.Web.OStatus
lain's avatar
lain committed
11
  import Ecto.Query
lain's avatar
lain committed
12
  import Pleroma.Web.ActivityPub.Utils
lain's avatar
lain committed
13
  require Logger
lain's avatar
lain committed
14

lain's avatar
lain committed
15 16
  @httpoison Application.get_env(:pleroma, :httpoison)

17 18
  # 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.
19 20 21 22
  defp get_recipients(%{"type" => "Announce"} = data) do
    to = data["to"] || []
    cc = data["cc"] || []
    recipients = to ++ cc
23 24 25 26 27 28 29 30 31 32 33 34
    actor = User.get_cached_by_ap_id(data["actor"])

    recipients
    |> Enum.filter(fn recipient ->
      case User.get_cached_by_ap_id(recipient) do
        nil ->
          true

        user ->
          User.following?(user, actor)
      end
    end)
35 36

    {recipients, to, cc}
37 38
  end

39 40 41 42 43
  defp get_recipients(data) do
    to = data["to"] || []
    cc = data["cc"] || []
    recipients = to ++ cc
    {recipients, to, cc}
lain's avatar
lain committed
44 45
  end

46
  defp check_actor_is_active(actor) do
47 48
    if not is_nil(actor) do
      with user <- User.get_cached_by_ap_id(actor),
lain's avatar
lain committed
49
           false <- user.info.deactivated do
50 51 52 53
        :ok
      else
        _e -> :reject
      end
54 55 56 57 58
    else
      :ok
    end
  end

59 60 61 62 63 64 65
  defp check_remote_limit(%{"object" => %{"content" => content}}) do
    limit = Pleroma.Config.get([:instance, :remote_limit])
    String.length(content) <= limit
  end

  defp check_remote_limit(_), do: true

lain's avatar
lain committed
66
  def insert(map, local \\ true) when is_map(map) do
67
    with nil <- Activity.normalize(map),
lain's avatar
lain committed
68
         map <- lazy_put_activity_defaults(map),
69
         :ok <- check_actor_is_active(map["actor"]),
70
         {_, true} <- {:remote_limit_error, check_remote_limit(map)},
href's avatar
href committed
71
         {:ok, map} <- MRF.filter(map),
lain's avatar
lain committed
72
         :ok <- insert_full_object(map) do
73
      {recipients, _, _} = get_recipients(map)
74

lain's avatar
lain committed
75 76 77 78 79
      {:ok, activity} =
        Repo.insert(%Activity{
          data: map,
          local: local,
          actor: map["actor"],
80
          recipients: recipients
lain's avatar
lain committed
81 82
        })

83
      Notification.create_notifications(activity)
lain's avatar
lain committed
84
      stream_out(activity)
85
      {:ok, activity}
lain's avatar
lain committed
86 87 88
    else
      %Activity{} = activity -> {:ok, activity}
      error -> {:error, error}
lain's avatar
lain committed
89
    end
lain's avatar
lain committed
90
  end
lain's avatar
lain committed
91

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

lain's avatar
lain committed
95 96
    if activity.data["type"] in ["Create", "Announce"] do
      Pleroma.Web.Streamer.stream("user", activity)
eal's avatar
eal committed
97
      Pleroma.Web.Streamer.stream("list", activity)
lain's avatar
lain committed
98

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

csaurus's avatar
csaurus committed
102 103 104
        if activity.local do
          Pleroma.Web.Streamer.stream("public:local", activity)
        end
105

106 107 108
        activity.data["object"]
        |> Map.get("tag", [])
        |> Enum.filter(fn tag -> is_bitstring(tag) end)
109 110
        |> Enum.map(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end)

111 112 113 114 115 116 117
        if activity.data["object"]["attachment"] != [] do
          Pleroma.Web.Streamer.stream("public:media", activity)

          if activity.local do
            Pleroma.Web.Streamer.stream("public:local:media", activity)
          end
        end
csaurus's avatar
csaurus committed
118 119 120 121 122
      else
        if !Enum.member?(activity.data["cc"] || [], public) &&
             !Enum.member?(
               activity.data["to"],
               User.get_by_ap_id(activity.data["actor"]).follower_address
csaurus's avatar
csaurus committed
123 124
             ),
           do: Pleroma.Web.Streamer.stream("direct", activity)
lain's avatar
lain committed
125
      end
lain's avatar
lain committed
126 127 128
    end
  end

lain's avatar
lain committed
129 130
  def create(%{to: to, actor: actor, context: context, object: object} = params) do
    additional = params[:additional] || %{}
lain's avatar
lain committed
131 132
    # only accept false as false value
    local = !(params[:local] == false)
lain's avatar
lain committed
133 134
    published = params[:published]

lain's avatar
lain committed
135 136 137 138 139
    with create_data <-
           make_create_data(
             %{to: to, actor: actor, published: published, context: context, object: object},
             additional
           ),
lain's avatar
lain committed
140
         {:ok, activity} <- insert(create_data, local),
eal's avatar
eal committed
141
         :ok <- maybe_federate(activity),
feld's avatar
feld committed
142
         {:ok, _actor} <- User.increase_note_count(actor) do
143 144
      {:ok, activity}
    end
lain's avatar
lain committed
145
  end
lain's avatar
lain committed
146

147
  def accept(%{to: to, actor: actor, object: object} = params) do
lain's avatar
lain committed
148 149
    # only accept false as false value
    local = !(params[:local] == false)
150 151 152 153 154 155 156 157

    with data <- %{"to" => to, "type" => "Accept", "actor" => actor, "object" => object},
         {:ok, activity} <- insert(data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
    end
  end

158 159 160 161 162 163 164 165 166 167 168
  def reject(%{to: to, actor: actor, object: object} = params) do
    # only accept false as false value
    local = !(params[:local] == false)

    with data <- %{"to" => to, "type" => "Reject", "actor" => actor, "object" => object},
         {:ok, activity} <- insert(data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
    end
  end

lain's avatar
lain committed
169
  def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
lain's avatar
lain committed
170 171 172 173 174 175 176 177 178 179
    # 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
180 181 182 183 184 185
         {:ok, activity} <- insert(data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
    end
  end

lain's avatar
lain committed
186
  # TODO: This is weird, maybe we shouldn't check here if we can make the activity.
lain's avatar
lain committed
187 188 189 190 191 192
  def like(
        %User{ap_id: ap_id} = user,
        %Object{data: %{"id" => _}} = object,
        activity_id \\ nil,
        local \\ true
      ) do
lain's avatar
lain committed
193 194 195 196 197 198 199 200 201
    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
202
    end
lain's avatar
lain committed
203 204
  end

Thog's avatar
Thog committed
205 206 207 208 209 210 211 212 213 214 215 216 217
  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
218 219
    else
      _e -> {:ok, object}
lain's avatar
lain committed
220 221 222
    end
  end

lain's avatar
lain committed
223 224 225 226 227 228
  def announce(
        %User{ap_id: _} = user,
        %Object{data: %{"id" => _}} = object,
        activity_id \\ nil,
        local \\ true
      ) do
lain's avatar
lain committed
229 230
    with true <- is_public?(object),
         announce_data <- make_announce_data(user, object, activity_id),
lain's avatar
lain committed
231 232 233 234 235 236 237
         {: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
238 239
  end

240 241 242
  def unannounce(
        %User{} = actor,
        %Object{} = object,
243 244
        activity_id \\ nil,
        local \\ true
245
      ) do
normandy's avatar
normandy committed
246 247
    with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object),
         unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id),
248
         {:ok, unannounce_activity} <- insert(unannounce_data, local),
normandy's avatar
normandy committed
249 250 251
         :ok <- maybe_federate(unannounce_activity),
         {:ok, _activity} <- Repo.delete(announce_activity),
         {:ok, object} <- remove_announce_from_object(announce_activity, object) do
252
      {:ok, unannounce_activity, object}
normandy's avatar
normandy committed
253 254 255 256 257
    else
      _e -> {:ok, object}
    end
  end

lain's avatar
lain committed
258 259 260 261 262 263
  def follow(follower, followed, activity_id \\ nil, local \\ true) do
    with data <- make_follow_data(follower, followed, activity_id),
         {:ok, activity} <- insert(data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
    end
lain's avatar
lain committed
264 265
  end

normandy's avatar
normandy committed
266
  def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
lain's avatar
lain committed
267
    with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
268
         {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"),
normandy's avatar
normandy committed
269
         unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
lain's avatar
lain committed
270
         {:ok, activity} <- insert(unfollow_data, local),
normandy's avatar
normandy committed
271
         :ok <- maybe_federate(activity) do
lain's avatar
lain committed
272 273
      {:ok, activity}
    end
lain's avatar
lain committed
274 275
  end

lain's avatar
lain committed
276 277
  def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
    user = User.get_cached_by_ap_id(actor)
lain's avatar
lain committed
278

lain's avatar
lain committed
279 280 281 282 283 284
    data = %{
      "type" => "Delete",
      "actor" => actor,
      "object" => id,
      "to" => [user.follower_address, "https://www.w3.org/ns/activitystreams#Public"]
    }
lain's avatar
lain committed
285

286
    with {:ok, _} <- Object.delete(object),
lain's avatar
lain committed
287
         {:ok, activity} <- insert(data, local),
288
         :ok <- maybe_federate(activity),
feld's avatar
feld committed
289
         {:ok, _actor} <- User.decrease_note_count(user) do
lain's avatar
lain committed
290 291 292 293
      {:ok, activity}
    end
  end

normandy's avatar
normandy committed
294
  def block(blocker, blocked, activity_id \\ nil, local \\ true) do
295 296 297
    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
298

299
    with true <- unfollow_blocked do
300
      follow_activity = fetch_latest_follow(blocker, blocked)
squidboi's avatar
squidboi committed
301

302 303 304
      if follow_activity do
        unfollow(blocker, blocked, nil, local)
      end
normandy's avatar
normandy committed
305 306
    end

307 308
    with true <- outgoing_blocks,
         block_data <- make_block_data(blocker, blocked, activity_id),
normandy's avatar
normandy committed
309 310 311
         {:ok, activity} <- insert(block_data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
312
    else
squidboi's avatar
squidboi committed
313
      _e -> {:ok, nil}
normandy's avatar
normandy committed
314 315 316
    end
  end

normandy's avatar
normandy committed
317
  def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do
normandy's avatar
normandy committed
318
    with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked),
normandy's avatar
normandy committed
319
         unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id),
normandy's avatar
normandy committed
320 321 322 323 324 325
         {:ok, activity} <- insert(unblock_data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
    end
  end

326
  def fetch_activities_for_context(context, opts \\ %{}) do
lain's avatar
lain committed
327 328
    public = ["https://www.w3.org/ns/activitystreams#Public"]

lain's avatar
lain committed
329 330 331 332 333 334 335
    recipients =
      if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public

    query = from(activity in Activity)

    query =
      query
lain's avatar
lain committed
336
      |> restrict_blocked(opts)
lain's avatar
lain committed
337 338
      |> restrict_recipients(recipients, opts["user"])

lain's avatar
lain committed
339 340 341 342 343 344 345 346 347 348 349 350 351 352
    query =
      from(
        activity in query,
        where:
          fragment(
            "?->>'type' = ? and ?->>'context' = ?",
            activity.data,
            "Create",
            activity.data,
            ^context
          ),
        order_by: [desc: :id]
      )

lain's avatar
lain committed
353
    Repo.all(query)
lain's avatar
lain committed
354 355
  end

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

lain's avatar
lain committed
359
    q
360
    |> restrict_unlisted()
lain's avatar
lain committed
361 362
    |> Repo.all()
    |> Enum.reverse()
lain's avatar
lain committed
363 364
  end

365 366 367 368 369 370 371 372 373
  @valid_visibilities ~w[direct unlisted public private]

  defp restrict_visibility(query, %{visibility: "direct"}) do
    public = "https://www.w3.org/ns/activitystreams#Public"

    from(
      activity in query,
      join: sender in User,
      on: sender.ap_id == activity.actor,
374
      # Are non-direct statuses with no to/cc possible?
375
      where:
lain's avatar
lain committed
376 377 378 379 380
        fragment(
          "not (? && ?)",
          [^public, sender.follower_address],
          activity.recipients
        )
381 382 383 384 385 386 387 388 389 390
    )
  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

391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
  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)

    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

lain's avatar
lain committed
410
  defp restrict_since(query, %{"since_id" => since_id}) do
lain's avatar
lain committed
411
    from(activity in query, where: activity.id > ^since_id)
lain's avatar
lain committed
412
  end
lain's avatar
lain committed
413

lain's avatar
lain committed
414
  defp restrict_since(query, _), do: query
lain's avatar
lain committed
415

Roger Braun's avatar
Roger Braun committed
416
  defp restrict_tag(query, %{"tag" => tag}) do
lain's avatar
lain committed
417 418
    from(
      activity in query,
Roger Braun's avatar
Roger Braun committed
419
      where: fragment("? <@ (? #> '{\"object\",\"tag\"}')", ^tag, activity.data)
lain's avatar
lain committed
420
    )
Roger Braun's avatar
Roger Braun committed
421
  end
lain's avatar
lain committed
422

Roger Braun's avatar
Roger Braun committed
423 424
  defp restrict_tag(query, _), do: query

425 426 427 428 429
  defp restrict_to_cc(query, recipients_to, recipients_cc) do
    from(
      activity in query,
      where:
        fragment(
430 431
          "(?->'to' \\?| ?) or (?->'cc' \\?| ?)",
          activity.data,
432
          ^recipients_to,
433 434
          activity.data,
          ^recipients_cc
435 436 437 438
        )
    )
  end

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

lain's avatar
lain committed
441
  defp restrict_recipients(query, recipients, nil) do
lain's avatar
lain committed
442
    from(activity in query, where: fragment("? && ?", ^recipients, activity.recipients))
lain's avatar
lain committed
443
  end
lain's avatar
lain committed
444

lain's avatar
lain committed
445
  defp restrict_recipients(query, recipients, user) do
lain's avatar
lain committed
446 447
    from(
      activity in query,
lain's avatar
lain committed
448 449
      where: fragment("? && ?", ^recipients, activity.recipients),
      or_where: activity.actor == ^user.ap_id
lain's avatar
lain committed
450
    )
lain's avatar
lain committed
451
  end
lain's avatar
lain committed
452

kaniini's avatar
kaniini committed
453
  defp restrict_limit(query, %{"limit" => limit}) do
lain's avatar
lain committed
454
    from(activity in query, limit: ^limit)
kaniini's avatar
kaniini committed
455
  end
lain's avatar
lain committed
456

kaniini's avatar
kaniini committed
457 458
  defp restrict_limit(query, _), do: query

lain's avatar
lain committed
459
  defp restrict_local(query, %{"local_only" => true}) do
lain's avatar
lain committed
460
    from(activity in query, where: activity.local == true)
lain's avatar
lain committed
461
  end
lain's avatar
lain committed
462

lain's avatar
lain committed
463
  defp restrict_local(query, _), do: query
lain's avatar
lain committed
464

lain's avatar
lain committed
465
  defp restrict_max(query, %{"max_id" => max_id}) do
lain's avatar
lain committed
466
    from(activity in query, where: activity.id < ^max_id)
467
  end
lain's avatar
lain committed
468

lain's avatar
lain committed
469
  defp restrict_max(query, _), do: query
470

lain's avatar
lain committed
471
  defp restrict_actor(query, %{"actor_id" => actor_id}) do
lain's avatar
lain committed
472
    from(activity in query, where: activity.actor == ^actor_id)
473
  end
lain's avatar
lain committed
474

lain's avatar
lain committed
475
  defp restrict_actor(query, _), do: query
476

lain's avatar
lain committed
477 478 479
  defp restrict_type(query, %{"type" => type}) when is_binary(type) do
    restrict_type(query, %{"type" => [type]})
  end
lain's avatar
lain committed
480

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

485 486
  defp restrict_type(query, _), do: query

487
  defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
lain's avatar
lain committed
488 489
    from(
      activity in query,
490
      where: fragment("? <@ (? #> '{\"object\",\"likes\"}')", ^ap_id, activity.data)
lain's avatar
lain committed
491
    )
492
  end
lain's avatar
lain committed
493

494 495
  defp restrict_favorited_by(query, _), do: query

eal's avatar
eal committed
496
  defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
lain's avatar
lain committed
497 498
    from(
      activity in query,
eal's avatar
eal committed
499
      where: fragment("not (? #> '{\"object\",\"attachment\"}' = ?)", activity.data, ^[])
lain's avatar
lain committed
500
    )
eal's avatar
eal committed
501
  end
lain's avatar
lain committed
502

eal's avatar
eal committed
503 504
  defp restrict_media(query, _), do: query

505 506 507 508 509 510 511 512 513
  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

514 515 516 517 518 519
  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

520
  # Only search through last 100_000 activities by default
521
  defp restrict_recent(query, %{"whole_db" => true}), do: query
lain's avatar
lain committed
522

523
  defp restrict_recent(query, _) do
lain's avatar
lain committed
524
    since = (Repo.aggregate(Activity, :max, :id) || 0) - 100_000
525

lain's avatar
lain committed
526
    from(activity in query, where: activity.id > ^since)
527 528
  end

lain's avatar
lain committed
529
  defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
lain's avatar
lain committed
530 531
    blocks = info.blocks || []
    domain_blocks = info.domain_blocks || []
lain's avatar
lain committed
532 533 534

    from(
      activity in query,
535
      where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
536
      where: fragment("not (?->'to' \\?| ?)", activity.data, ^blocks),
eal's avatar
eal committed
537
      where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks)
538
    )
539
  end
lain's avatar
lain committed
540

541 542
  defp restrict_blocked(query, _), do: query

543 544 545
  defp restrict_unlisted(query) do
    from(
      activity in query,
lain's avatar
Format.  
lain committed
546 547
      where:
        fragment(
lain's avatar
lain committed
548
          "not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)",
lain's avatar
Format.  
lain committed
549 550 551
          activity.data,
          ^["https://www.w3.org/ns/activitystreams#Public"]
        )
552 553 554
    )
  end

lain's avatar
lain committed
555
  def fetch_activities_query(recipients, opts \\ %{}) do
lain's avatar
lain committed
556 557 558 559 560 561
    base_query =
      from(
        activity in Activity,
        limit: 20,
        order_by: [fragment("? desc nulls last", activity.id)]
      )
lain's avatar
lain committed
562

lain's avatar
lain committed
563
    base_query
lain's avatar
lain committed
564
    |> restrict_recipients(recipients, opts["user"])
Roger Braun's avatar
Roger Braun committed
565
    |> restrict_tag(opts)
lain's avatar
lain committed
566 567
    |> restrict_since(opts)
    |> restrict_local(opts)
kaniini's avatar
kaniini committed
568
    |> restrict_limit(opts)
lain's avatar
lain committed
569 570
    |> restrict_max(opts)
    |> restrict_actor(opts)
571
    |> restrict_type(opts)
572
    |> restrict_favorited_by(opts)
573
    |> restrict_recent(opts)
574
    |> restrict_blocked(opts)
eal's avatar
eal committed
575
    |> restrict_media(opts)
576
    |> restrict_visibility(opts)
577
    |> restrict_replies(opts)
578
    |> restrict_reblogs(opts)
lain's avatar
lain committed
579 580 581 582
  end

  def fetch_activities(recipients, opts \\ %{}) do
    fetch_activities_query(recipients, opts)
lain's avatar
lain committed
583 584
    |> Repo.all()
    |> Enum.reverse()
dtluna's avatar
dtluna committed
585 586
  end

587 588 589 590 591 592 593
  def fetch_activities_bounded(recipients_to, recipients_cc, opts \\ %{}) do
    fetch_activities_query([], opts)
    |> restrict_to_cc(recipients_to, recipients_cc)
    |> Repo.all()
    |> Enum.reverse()
  end

href's avatar
href committed
594 595
  def upload(file, opts \\ []) do
    with {:ok, data} <- Upload.store(file, opts) do
596 597 598 599 600 601 602
      obj_data =
        if opts[:actor] do
          Map.put(data, "actor", opts[:actor])
        else
          data
        end

603
      Repo.insert(%Object{data: obj_data})
604
    end
lain's avatar
lain committed
605
  end
lain's avatar
lain committed
606

lain's avatar
lain committed
607
  def user_data_from_user_object(data) do
lain's avatar
lain committed
608 609 610 611 612 613 614 615 616 617 618 619 620
    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
621

622
    locked = data["manuallyApprovesFollowers"] || false
623 624
    data = Transmogrifier.maybe_fix_user_object(data)

lain's avatar
lain committed
625 626 627 628 629
    user_data = %{
      ap_id: data["id"],
      info: %{
        "ap_enabled" => true,
        "source_data" => data,
630 631
        "banner" => banner,
        "locked" => locked
lain's avatar
lain committed
632 633 634 635 636 637 638
      },
      avatar: avatar,
      name: data["name"],
      follower_address: data["followers"],
      bio: data["summary"]
    }

639 640 641
    # nickname can be nil because of virtual actors
    user_data =
      if data["preferredUsername"] do
kaniini's avatar
kaniini committed
642 643 644 645 646
        Map.put(
          user_data,
          :nickname,
          "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}"
        )
647 648 649 650
      else
        Map.put(user_data, :nickname, nil)
      end

lain's avatar
lain committed
651 652 653
    {:ok, user_data}
  end

lain's avatar
lain committed
654
  def fetch_and_prepare_user_from_ap_id(ap_id) do
655
    with {:ok, data} <- fetch_and_contain_remote_object_from_id(ap_id) do
lain's avatar
lain committed
656
      user_data_from_user_object(data)
lain's avatar
lain committed
657
    else
658
      e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
lain's avatar
lain committed
659 660 661 662
    end
  end

  def make_user_from_ap_id(ap_id) do
feld's avatar
feld committed
663
    if _user = User.get_by_ap_id(ap_id) do
lain's avatar
lain committed
664 665 666 667
      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)
lain's avatar
lain committed
668
      else
lain's avatar
lain committed
669
        e -> {:error, e}
lain's avatar
lain committed
670
      end
lain's avatar
lain committed
671 672
    end
  end
673

lain's avatar
lain committed
674 675 676
  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
677
    else
feld's avatar
feld committed
678
      _e -> {:error, "No AP id in WebFinger"}
lain's avatar
lain committed
679 680 681
    end
  end

682 683 684 685 686
  def should_federate?(inbox, public) do
    if public do
      true
    else
      inbox_info = URI.parse(inbox)
href's avatar
href committed
687
      !Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
688 689 690
    end
  end

691
  def publish(actor, activity) do
lain's avatar
lain committed
692 693 694 695 696 697 698
    followers =
      if actor.follower_address in activity.recipients do
        {:ok, followers} = User.get_followers(actor)
        followers |> Enum.filter(&(!&1.local))
      else
        []
      end
699

700 701
    public = is_public?(activity)

lain's avatar
lain committed
702 703 704
    remote_inboxes =
      (Pleroma.Web.Salmon.remote_users(activity) ++ followers)
      |> Enum.filter(fn user -> User.ap_enabled?(user) end)
lain's avatar
lain committed
705
      |> Enum.map(fn %{info: %{source_data: data}} ->
706
        (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
lain's avatar
lain committed
707 708
      end)
      |> Enum.uniq()
709
      |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
710

lain's avatar
lain committed
711
    {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
lain's avatar
lain committed
712
    json = Jason.encode!(data)
lain's avatar
lain committed
713 714 715 716 717 718 719 720 721

    Enum.each(remote_inboxes, fn inbox ->
      Federator.enqueue(:publish_single_ap, %{
        inbox: inbox,
        json: json,
        actor: actor,
        id: activity.data["id"]
      })
    end)
722
  end
723

lain's avatar
lain committed
724 725 726
  def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do
    Logger.info("Federating #{id} to #{inbox}")
    host = URI.parse(inbox).host
lain's avatar
lain committed
727

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

lain's avatar
lain committed
730
    signature =
731 732 733 734 735
      Pleroma.Web.HTTPSignatures.sign(actor, %{
        host: host,
        "content-length": byte_size(json),
        digest: digest
      })
lain's avatar
lain committed
736 737 738 739

    @httpoison.post(
      inbox,
      json,
740 741 742 743 744
      [
        {"Content-Type", "application/activity+json"},
        {"signature", signature},
        {"digest", digest}
      ],
lain's avatar
lain committed
745 746
      hackney: [pool: :default]
    )
lain's avatar
lain committed
747 748
  end

749 750
  # TODO:
  # This will create a Create activity, which we need internally at the moment.
751 752 753 754
  def fetch_object_from_id(id) do
    if object = Object.get_cached_by_ap_id(id) do
      {:ok, object}
    else
lain's avatar
lain committed
755
      Logger.info("Fetching #{id} via AP")
lain's avatar
lain committed
756

757
      with {:ok, data} <- fetch_and_contain_remote_object_from_id(id),
758
           nil <- Object.normalize(data),
lain's avatar
lain committed
759 760 761 762
           params <- %{
             "type" => "Create",
             "to" => data["to"],
             "cc" => data["cc"],
763
             "actor" => data["actor"] || data["attributedTo"],
lain's avatar
lain committed
764 765
             "object" => data
           },
766
           :ok <- Transmogrifier.contain_origin(id, params),
767
           {:ok, activity} <- Transmogrifier.handle_incoming(params) do
768
        {:ok, Object.normalize(activity.data["object"])}
769
      else
770
        {:error, {:reject, nil}} ->
771 772
          {:reject, nil}

lain's avatar
lain committed
773 774 775
        object = %Object{} ->
          {:ok, object}

feld's avatar
feld committed
776
        _e ->
lain's avatar
lain committed
777
          Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
lain's avatar
lain committed
778

lain's avatar
lain committed
779
          case OStatus.fetch_activity_from_url(id) do
780
            {:ok, [activity | _]} -> {:ok, Object.normalize(activity.data["object"])}
lain's avatar
lain committed
781 782
            e -> e
          end
783 784 785
      end
    end
  end
lain's avatar
lain committed
786

787 788 789 790
  def fetch_and_contain_remote_object_from_id(id) do
    Logger.info("Fetching #{id} via AP")

    with true <- String.starts_with?(id, "http"),
Maksim's avatar
Maksim committed
791
         {:ok, %{body: body, status: code}} when code in 200..299 <-
792 793
           @httpoison.get(
             id,
Hakaba Hitoyo's avatar
Hakaba Hitoyo committed
794
             [{:Accept, "application/activity+json"}]
795 796 797 798 799 800 801 802 803 804
           ),
         {:ok, data} <- Jason.decode(body),
         :ok <- Transmogrifier.contain_origin_from_id(id, data) do
      {:ok, data}
    else
      e ->
        {:error, e}
    end
  end

lain's avatar
lain committed
805
  def is_public?(activity) do
lain's avatar
lain committed
806 807
    "https://www.w3.org/ns/activitystreams#Public" in (activity.data["to"] ++
                                                         (activity.data["cc"] || []))
lain's avatar
lain committed
808
  end
lain's avatar
lain committed
809 810 811 812

  def visible_for_user?(activity, nil) do
    is_public?(activity)
  end
lain's avatar
lain committed
813

lain's avatar
lain committed
814 815
  def visible_for_user?(activity, user) do
    x = [user.ap_id | user.following]
lain's avatar
lain committed
816
    y = activity.data["to"] ++ (activity.data["cc"] || [])
lain's avatar
lain committed
817 818
    visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
  end
819 820

  # guard
Maksim's avatar
Maksim committed
821
  def entire_thread_visible_for_user?(nil, _user), do: false
822 823 824

  # child
  def entire_thread_visible_for_user?(
825
        %Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail,
826
        user
827 828
      )
      when is_binary(parent_id) do
829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852
    parent = Activity.get_in_reply_to_activity(tail)
    visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user)
  end

  # root
  def entire_thread_visible_for_user?(tail, user), do: visible_for_user?(tail, user)

  # 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
    contain_broken_threads(activity, user)
  end

  # do post-processing on a timeline
  def contain_timeline(timeline, user) do
    timeline
    |> Enum.filter(fn activity ->
      contain_activity(activity, user)
    end)
  end
lain's avatar
lain committed
853
end