activity_pub.ex 25.6 KB
Newer Older
1
# Pleroma: A lightweight social networking server
kaniini's avatar
kaniini committed
2
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 4
# SPDX-License-Identifier: AGPL-3.0-only

lain's avatar
lain committed
5
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

Maxim Filippov's avatar
Maxim Filippov committed
39 40 41 42 43 44 45 46
  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

47 48 49 50 51
  defp get_recipients(data) do
    to = data["to"] || []
    cc = data["cc"] || []
    recipients = to ++ cc
    {recipients, to, cc}
lain's avatar
lain committed
52 53
  end

54
  defp check_actor_is_active(actor) do
55 56
    if not is_nil(actor) do
      with user <- User.get_cached_by_ap_id(actor),
lain's avatar
lain committed
57
           false <- user.info.deactivated do
58 59 60 61
        :ok
      else
        _e -> :reject
      end
62 63 64 65 66
    else
      :ok
    end
  end

67
  defp check_remote_limit(%{"object" => %{"content" => content}}) when not is_nil(content) do
68 69 70 71 72 73
    limit = Pleroma.Config.get([:instance, :remote_limit])
    String.length(content) <= limit
  end

  defp check_remote_limit(_), do: true

lain's avatar
lain committed
74
  def insert(map, local \\ true) when is_map(map) do
75
    with nil <- Activity.normalize(map),
lain's avatar
lain committed
76
         map <- lazy_put_activity_defaults(map),
77
         :ok <- check_actor_is_active(map["actor"]),
78
         {_, true} <- {:remote_limit_error, check_remote_limit(map)},
href's avatar
href committed
79
         {:ok, map} <- MRF.filter(map),
lain's avatar
lain committed
80
         :ok <- insert_full_object(map) do
81
      {recipients, _, _} = get_recipients(map)
82

lain's avatar
lain committed
83 84 85 86 87
      {:ok, activity} =
        Repo.insert(%Activity{
          data: map,
          local: local,
          actor: map["actor"],
88
          recipients: recipients
lain's avatar
lain committed
89 90
        })

91
      Notification.create_notifications(activity)
lain's avatar
lain committed
92
      stream_out(activity)
93
      {:ok, activity}
lain's avatar
lain committed
94 95 96
    else
      %Activity{} = activity -> {:ok, activity}
      error -> {:error, error}
lain's avatar
lain committed
97
    end
lain's avatar
lain committed
98
  end
lain's avatar
lain committed
99

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

103
    if activity.data["type"] in ["Create", "Announce", "Delete"] do
lain's avatar
lain committed
104
      Pleroma.Web.Streamer.stream("user", activity)
eal's avatar
eal committed
105
      Pleroma.Web.Streamer.stream("list", activity)
lain's avatar
lain committed
106

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

csaurus's avatar
csaurus committed
110 111 112
        if activity.local do
          Pleroma.Web.Streamer.stream("public:local", activity)
        end
113

114 115 116 117 118
        if activity.data["type"] in ["Create"] do
          activity.data["object"]
          |> Map.get("tag", [])
          |> Enum.filter(fn tag -> is_bitstring(tag) end)
          |> Enum.map(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end)
119

120 121
          if activity.data["object"]["attachment"] != [] do
            Pleroma.Web.Streamer.stream("public:media", activity)
122

123 124 125
            if activity.local do
              Pleroma.Web.Streamer.stream("public:local:media", activity)
            end
126 127
          end
        end
csaurus's avatar
csaurus committed
128 129 130 131 132
      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
133 134
             ),
           do: Pleroma.Web.Streamer.stream("direct", activity)
lain's avatar
lain committed
135
      end
lain's avatar
lain committed
136 137 138
    end
  end

lain's avatar
lain committed
139 140
  def create(%{to: to, actor: actor, context: context, object: object} = params) do
    additional = params[:additional] || %{}
lain's avatar
lain committed
141 142
    # only accept false as false value
    local = !(params[:local] == false)
lain's avatar
lain committed
143 144
    published = params[:published]

lain's avatar
lain committed
145 146 147 148 149
    with create_data <-
           make_create_data(
             %{to: to, actor: actor, published: published, context: context, object: object},
             additional
           ),
lain's avatar
lain committed
150
         {:ok, activity} <- insert(create_data, local),
Ivan Tashkinov's avatar
Ivan Tashkinov committed
151
         # Changing note count prior to enqueuing federation task in order to avoid race conditions on updating user.info
152 153
         {:ok, _actor} <- User.increase_note_count(actor),
         :ok <- maybe_federate(activity) do
154 155
      {:ok, activity}
    end
lain's avatar
lain committed
156
  end
lain's avatar
lain committed
157

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

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

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

lain's avatar
lain committed
197
  # TODO: This is weird, maybe we shouldn't check here if we can make the activity.
lain's avatar
lain committed
198 199 200 201 202 203
  def like(
        %User{ap_id: ap_id} = user,
        %Object{data: %{"id" => _}} = object,
        activity_id \\ nil,
        local \\ true
      ) do
lain's avatar
lain committed
204 205 206 207 208 209 210 211 212
    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
213
    end
lain's avatar
lain committed
214 215
  end

Thog's avatar
Thog committed
216 217 218 219 220 221 222 223 224 225 226 227 228
  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
229 230
    else
      _e -> {:ok, object}
lain's avatar
lain committed
231 232 233
    end
  end

lain's avatar
lain committed
234 235 236 237
  def announce(
        %User{ap_id: _} = user,
        %Object{data: %{"id" => _}} = object,
        activity_id \\ nil,
238 239
        local \\ true,
        public \\ true
lain's avatar
lain committed
240
      ) do
lain's avatar
lain committed
241
    with true <- is_public?(object),
242
         announce_data <- make_announce_data(user, object, activity_id, public),
lain's avatar
lain committed
243 244 245 246 247 248 249
         {: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
250 251
  end

252 253 254
  def unannounce(
        %User{} = actor,
        %Object{} = object,
255 256
        activity_id \\ nil,
        local \\ true
257
      ) do
normandy's avatar
normandy committed
258 259
    with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object),
         unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id),
260
         {:ok, unannounce_activity} <- insert(unannounce_data, local),
normandy's avatar
normandy committed
261 262 263
         :ok <- maybe_federate(unannounce_activity),
         {:ok, _activity} <- Repo.delete(announce_activity),
         {:ok, object} <- remove_announce_from_object(announce_activity, object) do
264
      {:ok, unannounce_activity, object}
normandy's avatar
normandy committed
265 266 267 268 269
    else
      _e -> {:ok, object}
    end
  end

lain's avatar
lain committed
270 271 272 273 274 275
  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
276 277
  end

normandy's avatar
normandy committed
278
  def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
lain's avatar
lain committed
279
    with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
280
         {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"),
normandy's avatar
normandy committed
281
         unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
lain's avatar
lain committed
282
         {:ok, activity} <- insert(unfollow_data, local),
normandy's avatar
normandy committed
283
         :ok <- maybe_federate(activity) do
lain's avatar
lain committed
284 285
      {:ok, activity}
    end
lain's avatar
lain committed
286 287
  end

lain's avatar
lain committed
288 289
  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
290

lain's avatar
lain committed
291 292 293 294 295 296
    data = %{
      "type" => "Delete",
      "actor" => actor,
      "object" => id,
      "to" => [user.follower_address, "https://www.w3.org/ns/activitystreams#Public"]
    }
lain's avatar
lain committed
297

298
    with {:ok, _} <- Object.delete(object),
lain's avatar
lain committed
299
         {:ok, activity} <- insert(data, local),
Ivan Tashkinov's avatar
Ivan Tashkinov committed
300
         # Changing note count prior to enqueuing federation task in order to avoid race conditions on updating user.info
301 302
         {:ok, _actor} <- User.decrease_note_count(user),
         :ok <- maybe_federate(activity) do
lain's avatar
lain committed
303 304 305 306
      {:ok, activity}
    end
  end

normandy's avatar
normandy committed
307
  def block(blocker, blocked, activity_id \\ nil, local \\ true) do
308 309 310
    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
311

312
    with true <- unfollow_blocked do
313
      follow_activity = fetch_latest_follow(blocker, blocked)
squidboi's avatar
squidboi committed
314

315 316 317
      if follow_activity do
        unfollow(blocker, blocked, nil, local)
      end
normandy's avatar
normandy committed
318 319
    end

320 321
    with true <- outgoing_blocks,
         block_data <- make_block_data(blocker, blocked, activity_id),
normandy's avatar
normandy committed
322 323 324
         {:ok, activity} <- insert(block_data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
325
    else
squidboi's avatar
squidboi committed
326
      _e -> {:ok, nil}
normandy's avatar
normandy committed
327 328 329
    end
  end

normandy's avatar
normandy committed
330
  def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do
normandy's avatar
normandy committed
331
    with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked),
normandy's avatar
normandy committed
332
         unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id),
normandy's avatar
normandy committed
333 334 335 336 337 338
         {:ok, activity} <- insert(unblock_data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
    end
  end

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

lain's avatar
lain committed
342 343 344 345 346 347 348
    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
349
      |> restrict_blocked(opts)
lain's avatar
lain committed
350 351
      |> restrict_recipients(recipients, opts["user"])

lain's avatar
lain committed
352 353 354 355 356 357 358 359 360 361 362 363 364 365
    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
366
    Repo.all(query)
lain's avatar
lain committed
367 368
  end

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

lain's avatar
lain committed
372
    q
373
    |> restrict_unlisted()
lain's avatar
lain committed
374 375
    |> Repo.all()
    |> Enum.reverse()
lain's avatar
lain committed
376 377
  end

378 379
  @valid_visibilities ~w[direct unlisted public private]

lain's avatar
lain committed
380 381 382 383 384 385 386 387 388 389
  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)
390

lain's avatar
lain committed
391
    query
392 393 394 395 396 397 398 399 400
  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

401 402 403 404 405 406
  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
407
      |> Map.put("pinned_activity_ids", user.info.pinned_activities)
408 409 410 411 412 413 414 415 416 417 418 419 420

    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

421 422
  defp restrict_since(query, %{"since_id" => ""}), do: query

lain's avatar
lain committed
423
  defp restrict_since(query, %{"since_id" => since_id}) do
lain's avatar
lain committed
424
    from(activity in query, where: activity.id > ^since_id)
lain's avatar
lain committed
425
  end
lain's avatar
lain committed
426

lain's avatar
lain committed
427
  defp restrict_since(query, _), do: query
lain's avatar
lain committed
428

Roger Braun's avatar
Roger Braun committed
429
  defp restrict_tag(query, %{"tag" => tag}) do
lain's avatar
lain committed
430 431
    from(
      activity in query,
Roger Braun's avatar
Roger Braun committed
432
      where: fragment("? <@ (? #> '{\"object\",\"tag\"}')", ^tag, activity.data)
lain's avatar
lain committed
433
    )
Roger Braun's avatar
Roger Braun committed
434
  end
lain's avatar
lain committed
435

Roger Braun's avatar
Roger Braun committed
436 437
  defp restrict_tag(query, _), do: query

438 439 440 441 442
  defp restrict_to_cc(query, recipients_to, recipients_cc) do
    from(
      activity in query,
      where:
        fragment(
443 444
          "(?->'to' \\?| ?) or (?->'cc' \\?| ?)",
          activity.data,
445
          ^recipients_to,
446 447
          activity.data,
          ^recipients_cc
448 449 450 451
        )
    )
  end

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

lain's avatar
lain committed
454
  defp restrict_recipients(query, recipients, nil) do
lain's avatar
lain committed
455
    from(activity in query, where: fragment("? && ?", ^recipients, activity.recipients))
lain's avatar
lain committed
456
  end
lain's avatar
lain committed
457

lain's avatar
lain committed
458
  defp restrict_recipients(query, recipients, user) do
lain's avatar
lain committed
459 460
    from(
      activity in query,
lain's avatar
lain committed
461 462
      where: fragment("? && ?", ^recipients, activity.recipients),
      or_where: activity.actor == ^user.ap_id
lain's avatar
lain committed
463
    )
lain's avatar
lain committed
464
  end
lain's avatar
lain committed
465

kaniini's avatar
kaniini committed
466
  defp restrict_limit(query, %{"limit" => limit}) do
lain's avatar
lain committed
467
    from(activity in query, limit: ^limit)
kaniini's avatar
kaniini committed
468
  end
lain's avatar
lain committed
469

kaniini's avatar
kaniini committed
470 471
  defp restrict_limit(query, _), do: query

lain's avatar
lain committed
472
  defp restrict_local(query, %{"local_only" => true}) do
lain's avatar
lain committed
473
    from(activity in query, where: activity.local == true)
lain's avatar
lain committed
474
  end
lain's avatar
lain committed
475

lain's avatar
lain committed
476
  defp restrict_local(query, _), do: query
lain's avatar
lain committed
477

478 479
  defp restrict_max(query, %{"max_id" => ""}), do: query

lain's avatar
lain committed
480
  defp restrict_max(query, %{"max_id" => max_id}) do
lain's avatar
lain committed
481
    from(activity in query, where: activity.id < ^max_id)
482
  end
lain's avatar
lain committed
483

lain's avatar
lain committed
484
  defp restrict_max(query, _), do: query
485

lain's avatar
lain committed
486
  defp restrict_actor(query, %{"actor_id" => actor_id}) do
lain's avatar
lain committed
487
    from(activity in query, where: activity.actor == ^actor_id)
488
  end
lain's avatar
lain committed
489

lain's avatar
lain committed
490
  defp restrict_actor(query, _), do: query
491

lain's avatar
lain committed
492 493 494
  defp restrict_type(query, %{"type" => type}) when is_binary(type) do
    restrict_type(query, %{"type" => [type]})
  end
lain's avatar
lain committed
495

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

500 501
  defp restrict_type(query, _), do: query

502
  defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
lain's avatar
lain committed
503 504
    from(
      activity in query,
505
      where: fragment("? <@ (? #> '{\"object\",\"likes\"}')", ^ap_id, activity.data)
lain's avatar
lain committed
506
    )
507
  end
lain's avatar
lain committed
508

509 510
  defp restrict_favorited_by(query, _), do: query

eal's avatar
eal committed
511
  defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
lain's avatar
lain committed
512 513
    from(
      activity in query,
eal's avatar
eal committed
514
      where: fragment("not (? #> '{\"object\",\"attachment\"}' = ?)", activity.data, ^[])
lain's avatar
lain committed
515
    )
eal's avatar
eal committed
516
  end
lain's avatar
lain committed
517

eal's avatar
eal committed
518 519
  defp restrict_media(query, _), do: query

520 521 522 523 524 525 526 527 528
  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

529 530 531 532 533 534
  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
535
  defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
lain's avatar
lain committed
536 537
    blocks = info.blocks || []
    domain_blocks = info.domain_blocks || []
lain's avatar
lain committed
538 539 540

    from(
      activity in query,
541
      where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
542
      where: fragment("not (?->'to' \\?| ?)", activity.data, ^blocks),
eal's avatar
eal committed
543
      where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks)
544
    )
545
  end
lain's avatar
lain committed
546

547 548
  defp restrict_blocked(query, _), do: query

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

minibikini's avatar
minibikini committed
561 562 563 564 565 566
  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

lain's avatar
lain committed
567
  def fetch_activities_query(recipients, opts \\ %{}) do
lain's avatar
lain committed
568 569 570 571 572 573
    base_query =
      from(
        activity in Activity,
        limit: 20,
        order_by: [fragment("? desc nulls last", activity.id)]
      )
lain's avatar
lain committed
574

lain's avatar
lain committed
575
    base_query
lain's avatar
lain committed
576
    |> restrict_recipients(recipients, opts["user"])
Roger Braun's avatar
Roger Braun committed
577
    |> restrict_tag(opts)
lain's avatar
lain committed
578 579
    |> restrict_since(opts)
    |> restrict_local(opts)
kaniini's avatar
kaniini committed
580
    |> restrict_limit(opts)
lain's avatar
lain committed
581 582
    |> restrict_max(opts)
    |> restrict_actor(opts)
583
    |> restrict_type(opts)
584
    |> restrict_favorited_by(opts)
585
    |> restrict_blocked(opts)
eal's avatar
eal committed
586
    |> restrict_media(opts)
587
    |> restrict_visibility(opts)
588
    |> restrict_replies(opts)
589
    |> restrict_reblogs(opts)
minibikini's avatar
minibikini committed
590
    |> restrict_pinned(opts)
lain's avatar
lain committed
591 592 593 594
  end

  def fetch_activities(recipients, opts \\ %{}) do
    fetch_activities_query(recipients, opts)
lain's avatar
lain committed
595 596
    |> Repo.all()
    |> Enum.reverse()
dtluna's avatar
dtluna committed
597 598
  end

599 600 601 602 603 604 605
  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
606 607
  def upload(file, opts \\ []) do
    with {:ok, data} <- Upload.store(file, opts) do
608 609 610 611 612 613 614
      obj_data =
        if opts[:actor] do
          Map.put(data, "actor", opts[:actor])
        else
          data
        end

615
      Repo.insert(%Object{data: obj_data})
616
    end
lain's avatar
lain committed
617
  end
lain's avatar
lain committed
618

lain's avatar
lain committed
619
  def user_data_from_user_object(data) do
lain's avatar
lain committed
620 621 622 623 624 625 626 627 628 629 630 631 632
    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
633

634
    locked = data["manuallyApprovesFollowers"] || false
635 636
    data = Transmogrifier.maybe_fix_user_object(data)

lain's avatar
lain committed
637 638 639 640 641
    user_data = %{
      ap_id: data["id"],
      info: %{
        "ap_enabled" => true,
        "source_data" => data,
642 643
        "banner" => banner,
        "locked" => locked
lain's avatar
lain committed
644 645 646 647 648 649 650
      },
      avatar: avatar,
      name: data["name"],
      follower_address: data["followers"],
      bio: data["summary"]
    }

651 652 653
    # nickname can be nil because of virtual actors
    user_data =
      if data["preferredUsername"] do
kaniini's avatar
kaniini committed
654 655 656 657 658
        Map.put(
          user_data,
          :nickname,
          "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}"
        )
659 660 661 662
      else
        Map.put(user_data, :nickname, nil)
      end

lain's avatar
lain committed
663 664 665
    {:ok, user_data}
  end

lain's avatar
lain committed
666
  def fetch_and_prepare_user_from_ap_id(ap_id) do
667
    with {:ok, data} <- fetch_and_contain_remote_object_from_id(ap_id) do
lain's avatar
lain committed
668
      user_data_from_user_object(data)
lain's avatar
lain committed
669
    else
670
      e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
lain's avatar
lain committed
671 672 673 674
    end
  end

  def make_user_from_ap_id(ap_id) do
feld's avatar
feld committed
675
    if _user = User.get_by_ap_id(ap_id) do
lain's avatar
lain committed
676 677 678 679
      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
680
      else
lain's avatar
lain committed
681
        e -> {:error, e}
lain's avatar
lain committed
682
      end
lain's avatar
lain committed
683 684
    end
  end
685

lain's avatar
lain committed
686 687 688
  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
689
    else
feld's avatar
feld committed
690
      _e -> {:error, "No AP id in WebFinger"}
lain's avatar
lain committed
691 692 693
    end
  end

694 695 696 697 698
  def should_federate?(inbox, public) do
    if public do
      true
    else
      inbox_info = URI.parse(inbox)
href's avatar
href committed
699
      !Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
700 701 702
    end
  end

703
  def publish(actor, activity) do
lain's avatar
lain committed
704 705 706 707 708 709 710
    followers =
      if actor.follower_address in activity.recipients do
        {:ok, followers} = User.get_followers(actor)
        followers |> Enum.filter(&(!&1.local))
      else
        []
      end
711

712 713
    public = is_public?(activity)

lain's avatar
lain committed
714 715 716
    remote_inboxes =
      (Pleroma.Web.Salmon.remote_users(activity) ++ followers)
      |> Enum.filter(fn user -> User.ap_enabled?(user) end)
lain's avatar
lain committed
717
      |> Enum.map(fn %{info: %{source_data: data}} ->
718
        (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
lain's avatar
lain committed
719 720
      end)
      |> Enum.uniq()
721
      |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
722

lain's avatar
lain committed
723
    {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
lain's avatar
lain committed
724
    json = Jason.encode!(data)
lain's avatar
lain committed
725 726 727 728 729 730 731 732 733

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

lain's avatar
lain committed
736 737 738
  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
739

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

lain's avatar
lain committed
742
    signature =
743 744 745 746 747
      Pleroma.Web.HTTPSignatures.sign(actor, %{
        host: host,
        "content-length": byte_size(json),
        digest: digest
      })
lain's avatar
lain committed
748 749 750 751

    @httpoison.post(
      inbox,
      json,
752 753 754 755
      [
        {"Content-Type", "application/activity+json"},
        {"signature", signature},
        {"digest", digest}
756
      ]
lain's avatar
lain committed
757
    )
lain's avatar
lain committed
758 759
  end

760 761
  # TODO:
  # This will create a Create activity, which we need internally at the moment.
762 763 764 765
  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
766
      Logger.info("Fetching #{id} via AP")
lain's avatar
lain committed
767

768
      with {:ok, data} <- fetch_and_contain_remote_object_from_id(id),
769
           nil <- Object.normalize(data),
lain's avatar
lain committed
770 771 772 773
           params <- %{
             "type" => "Create",
             "to" => data["to"],
             "cc" => data["cc"],
774
             "actor" => data["actor"] || data["attributedTo"],
lain's avatar
lain committed
775 776
             "object" => data
           },
777
           :ok <- Transmogrifier.contain_origin(id, params),
778
           {:ok, activity} <- Transmogrifier.handle_incoming(params) do
779
        {:ok, Object.normalize(activity.data["object"])}
780
      else
781
        {:error, {:reject, nil}} ->
782 783
          {:reject, nil}

lain's avatar
lain committed
784 785 786
        object = %Object{} ->
          {:ok, object}

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

lain's avatar
lain committed
790
          case OStatus.fetch_activity_from_url(id) do
791
            {:ok, [activity | _]} -> {:ok, Object.normalize(activity.data["object"])}
lain's avatar
lain committed
792 793
            e -> e
          end
794 795 796
      end
    end
  end
lain's avatar
lain committed
797

798 799 800 801
  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
802
         {:ok, %{body: body, status: code}} when code in 200..299 <-
803 804
           @httpoison.get(
             id,
Hakaba Hitoyo's avatar
Hakaba Hitoyo committed
805
             [{:Accept, "application/activity+json"}]
806 807 808 809 810 811 812 813 814 815
           ),
         {:ok, data} <- Jason.decode(body),
         :ok <- Transmogrifier.contain_origin_from_id(id, data) do
      {:ok, data}
    else
      e ->
        {:error, e}
    end
  end

816
  def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
817
  def is_public?(%Object{data: data}), do: is_public?(data)
818
  def is_public?(%Activity{data: data}), do: is_public?(data)
819
  def is_public?(%{"directMessage" => true}), do: false
820

821 822
  def is_public?(data) do
    "https://www.w3.org/ns/activitystreams#Public" in (data["to"] ++ (data["cc"] || []))
lain's avatar
lain committed
823
  end
lain's avatar
lain committed
824

825 826 827 828
  def is_private?(activity) do
    !is_public?(activity) && Enum.any?(activity.data["to"], &String.contains?(&1, "/followers"))
  end

829 830 831
  def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true
  def is_direct?(%Object{data: %{"directMessage" => true}}), do: true

832 833 834 835
  def is_direct?(activity) do
    !is_public?(activity) && !is_private?(activity)
  end

lain's avatar
lain committed
836 837 838
  def visible_for_user?(activity, nil) do
    is_public?(activity)
  end
lain's avatar
lain committed
839

lain's avatar
lain committed
840 841
  def visible_for_user?(activity, user) do
    x = [user.ap_id | user.following]
lain's avatar
lain committed
842
    y = activity.data["to"] ++ (activity.data["cc"] || [])
lain's avatar
lain committed
843 844
    visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
  end
845 846

  # guard
Maksim's avatar
Maksim committed
847
  def entire_thread_visible_for_user?(nil, _user), do: false
848 849 850

  # child
  def entire_thread_visible_for_user?(
851
        %Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail,
852
        user
853 854
      )
      when is_binary(parent_id) do
855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878
    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
879
end