activity_pub.ex 27.3 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 7 8 9 10 11 12 13 14 15 16 17
  alias Pleroma.Activity
  alias Pleroma.Repo
  alias Pleroma.Object
  alias Pleroma.Upload
  alias Pleroma.User
  alias Pleroma.Notification
  alias Pleroma.Instances
  alias Pleroma.Web.ActivityPub.Transmogrifier
  alias Pleroma.Web.ActivityPub.MRF
  alias Pleroma.Web.WebFinger
  alias Pleroma.Web.Federator
  alias Pleroma.Web.OStatus
18

lain's avatar
lain committed
19
  import Ecto.Query
lain's avatar
lain committed
20
  import Pleroma.Web.ActivityPub.Utils
21

lain's avatar
lain committed
22
  require Logger
lain's avatar
lain committed
23

lain's avatar
lain committed
24 25
  @httpoison Application.get_env(:pleroma, :httpoison)

26 27
  # 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.
28 29 30
  defp get_recipients(%{"type" => "Announce"} = data) do
    to = data["to"] || []
    cc = data["cc"] || []
31 32
    actor = User.get_cached_by_ap_id(data["actor"])

33 34 35 36 37 38 39 40 41 42 43
    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)
44 45

    {recipients, to, cc}
46 47
  end

Maxim Filippov's avatar
Maxim Filippov committed
48 49 50 51 52 53 54 55
  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

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

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

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

  defp check_remote_limit(_), do: true

lain's avatar
lain committed
83
  def insert(map, local \\ true) when is_map(map) do
84
    with nil <- Activity.normalize(map),
lain's avatar
lain committed
85
         map <- lazy_put_activity_defaults(map),
86
         :ok <- check_actor_is_active(map["actor"]),
87
         {_, true} <- {:remote_limit_error, check_remote_limit(map)},
href's avatar
href committed
88
         {:ok, map} <- MRF.filter(map),
lain's avatar
lain committed
89
         :ok <- insert_full_object(map) do
90
      {recipients, _, _} = get_recipients(map)
91

lain's avatar
lain committed
92 93 94 95 96
      {:ok, activity} =
        Repo.insert(%Activity{
          data: map,
          local: local,
          actor: map["actor"],
97
          recipients: recipients
lain's avatar
lain committed
98 99
        })

100 101 102 103
      Task.start(fn ->
        Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
      end)

104
      Notification.create_notifications(activity)
lain's avatar
lain committed
105
      stream_out(activity)
106
      {:ok, activity}
lain's avatar
lain committed
107 108 109
    else
      %Activity{} = activity -> {:ok, activity}
      error -> {:error, error}
lain's avatar
lain committed
110
    end
lain's avatar
lain committed
111
  end
lain's avatar
lain committed
112

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

116
    if activity.data["type"] in ["Create", "Announce", "Delete"] do
lain's avatar
lain committed
117
      Pleroma.Web.Streamer.stream("user", activity)
eal's avatar
eal committed
118
      Pleroma.Web.Streamer.stream("list", activity)
lain's avatar
lain committed
119

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

csaurus's avatar
csaurus committed
123 124 125
        if activity.local do
          Pleroma.Web.Streamer.stream("public:local", activity)
        end
126

127 128 129 130
        if activity.data["type"] in ["Create"] do
          activity.data["object"]
          |> Map.get("tag", [])
          |> Enum.filter(fn tag -> is_bitstring(tag) end)
Haelwenn's avatar
Haelwenn committed
131
          |> Enum.each(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end)
132

133 134
          if activity.data["object"]["attachment"] != [] do
            Pleroma.Web.Streamer.stream("public:media", activity)
135

136 137 138
            if activity.local do
              Pleroma.Web.Streamer.stream("public:local:media", activity)
            end
139 140
          end
        end
csaurus's avatar
csaurus committed
141 142 143 144 145
      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
146 147
             ),
           do: Pleroma.Web.Streamer.stream("direct", activity)
lain's avatar
lain committed
148
      end
lain's avatar
lain committed
149 150 151
    end
  end

lain's avatar
lain committed
152 153
  def create(%{to: to, actor: actor, context: context, object: object} = params) do
    additional = params[:additional] || %{}
lain's avatar
lain committed
154 155
    # only accept false as false value
    local = !(params[:local] == false)
lain's avatar
lain committed
156 157
    published = params[:published]

lain's avatar
lain committed
158 159 160 161 162
    with create_data <-
           make_create_data(
             %{to: to, actor: actor, published: published, context: context, object: object},
             additional
           ),
lain's avatar
lain committed
163
         {:ok, activity} <- insert(create_data, local),
Ivan Tashkinov's avatar
Ivan Tashkinov committed
164
         # Changing note count prior to enqueuing federation task in order to avoid race conditions on updating user.info
165 166
         {:ok, _actor} <- User.increase_note_count(actor),
         :ok <- maybe_federate(activity) do
167 168
      {:ok, activity}
    end
lain's avatar
lain committed
169
  end
lain's avatar
lain committed
170

171
  def accept(%{to: to, actor: actor, object: object} = params) do
lain's avatar
lain committed
172 173
    # only accept false as false value
    local = !(params[:local] == false)
174 175 176 177 178 179 180 181

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

182 183 184 185 186 187 188 189 190 191 192
  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
193
  def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
lain's avatar
lain committed
194 195 196 197 198 199 200 201 202 203
    # 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
204 205 206 207 208 209
         {:ok, activity} <- insert(data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
    end
  end

lain's avatar
lain committed
210
  # TODO: This is weird, maybe we shouldn't check here if we can make the activity.
lain's avatar
lain committed
211 212 213 214 215 216
  def like(
        %User{ap_id: ap_id} = user,
        %Object{data: %{"id" => _}} = object,
        activity_id \\ nil,
        local \\ true
      ) do
lain's avatar
lain committed
217 218 219 220 221 222 223 224 225
    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
226
    end
lain's avatar
lain committed
227 228
  end

Thog's avatar
Thog committed
229 230 231 232 233 234 235 236 237 238 239 240 241
  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
242 243
    else
      _e -> {:ok, object}
lain's avatar
lain committed
244 245 246
    end
  end

lain's avatar
lain committed
247 248 249 250
  def announce(
        %User{ap_id: _} = user,
        %Object{data: %{"id" => _}} = object,
        activity_id \\ nil,
251 252
        local \\ true,
        public \\ true
lain's avatar
lain committed
253
      ) do
lain's avatar
lain committed
254
    with true <- is_public?(object),
255
         announce_data <- make_announce_data(user, object, activity_id, public),
lain's avatar
lain committed
256 257 258 259 260 261 262
         {: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
263 264
  end

265 266 267
  def unannounce(
        %User{} = actor,
        %Object{} = object,
268 269
        activity_id \\ nil,
        local \\ true
270
      ) do
normandy's avatar
normandy committed
271 272
    with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object),
         unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id),
273
         {:ok, unannounce_activity} <- insert(unannounce_data, local),
normandy's avatar
normandy committed
274 275 276
         :ok <- maybe_federate(unannounce_activity),
         {:ok, _activity} <- Repo.delete(announce_activity),
         {:ok, object} <- remove_announce_from_object(announce_activity, object) do
277
      {:ok, unannounce_activity, object}
normandy's avatar
normandy committed
278 279 280 281 282
    else
      _e -> {:ok, object}
    end
  end

lain's avatar
lain committed
283 284 285 286 287 288
  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
289 290
  end

normandy's avatar
normandy committed
291
  def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
lain's avatar
lain committed
292
    with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
293
         {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"),
normandy's avatar
normandy committed
294
         unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
lain's avatar
lain committed
295
         {:ok, activity} <- insert(unfollow_data, local),
normandy's avatar
normandy committed
296
         :ok <- maybe_federate(activity) do
lain's avatar
lain committed
297 298
      {:ok, activity}
    end
lain's avatar
lain committed
299 300
  end

lain's avatar
lain committed
301 302
  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
303

lain's avatar
lain committed
304 305 306 307 308 309
    data = %{
      "type" => "Delete",
      "actor" => actor,
      "object" => id,
      "to" => [user.follower_address, "https://www.w3.org/ns/activitystreams#Public"]
    }
lain's avatar
lain committed
310

311
    with {:ok, _} <- Object.delete(object),
lain's avatar
lain committed
312
         {:ok, activity} <- insert(data, local),
Ivan Tashkinov's avatar
Ivan Tashkinov committed
313
         # Changing note count prior to enqueuing federation task in order to avoid race conditions on updating user.info
314 315
         {:ok, _actor} <- User.decrease_note_count(user),
         :ok <- maybe_federate(activity) do
lain's avatar
lain committed
316 317 318 319
      {:ok, activity}
    end
  end

normandy's avatar
normandy committed
320
  def block(blocker, blocked, activity_id \\ nil, local \\ true) do
321 322 323
    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
324

325
    with true <- unfollow_blocked do
326
      follow_activity = fetch_latest_follow(blocker, blocked)
squidboi's avatar
squidboi committed
327

328 329 330
      if follow_activity do
        unfollow(blocker, blocked, nil, local)
      end
normandy's avatar
normandy committed
331 332
    end

333 334
    with true <- outgoing_blocks,
         block_data <- make_block_data(blocker, blocked, activity_id),
normandy's avatar
normandy committed
335 336 337
         {:ok, activity} <- insert(block_data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
338
    else
squidboi's avatar
squidboi committed
339
      _e -> {:ok, nil}
normandy's avatar
normandy committed
340 341 342
    end
  end

normandy's avatar
normandy committed
343
  def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do
normandy's avatar
normandy committed
344
    with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked),
normandy's avatar
normandy committed
345
         unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id),
normandy's avatar
normandy committed
346 347 348 349 350 351
         {:ok, activity} <- insert(unblock_data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
    end
  end

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

lain's avatar
lain committed
355 356 357 358 359 360 361
    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
362
      |> restrict_blocked(opts)
lain's avatar
lain committed
363 364
      |> restrict_recipients(recipients, opts["user"])

lain's avatar
lain committed
365 366 367 368 369 370 371 372 373 374 375 376 377 378
    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
379
    Repo.all(query)
lain's avatar
lain committed
380 381
  end

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

lain's avatar
lain committed
385
    q
386
    |> restrict_unlisted()
lain's avatar
lain committed
387 388
    |> Repo.all()
    |> Enum.reverse()
lain's avatar
lain committed
389 390
  end

391 392
  @valid_visibilities ~w[direct unlisted public private]

lain's avatar
lain committed
393 394 395 396 397 398 399 400 401 402
  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)
403

lain's avatar
lain committed
404
    query
405 406 407 408 409 410 411 412 413
  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

414 415 416 417 418 419
  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
420
      |> Map.put("pinned_activity_ids", user.info.pinned_activities)
421 422 423 424 425 426 427 428 429 430 431 432 433

    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

434 435
  defp restrict_since(query, %{"since_id" => ""}), do: query

lain's avatar
lain committed
436
  defp restrict_since(query, %{"since_id" => since_id}) do
lain's avatar
lain committed
437
    from(activity in query, where: activity.id > ^since_id)
lain's avatar
lain committed
438
  end
lain's avatar
lain committed
439

lain's avatar
lain committed
440
  defp restrict_since(query, _), do: query
lain's avatar
lain committed
441

Haelwenn's avatar
Haelwenn committed
442 443
  defp restrict_tag_reject(query, %{"tag_reject" => tag_reject})
       when is_list(tag_reject) and tag_reject != [] do
Haelwenn's avatar
Haelwenn committed
444 445
    from(
      activity in query,
446
      where: fragment("(not (? #> '{\"object\",\"tag\"}') \\?| ?)", activity.data, ^tag_reject)
447 448 449
    )
  end

Haelwenn's avatar
Haelwenn committed
450 451 452 453 454 455 456 457 458 459 460 461
  defp restrict_tag_reject(query, _), do: query

  defp restrict_tag_all(query, %{"tag_all" => tag_all})
       when is_list(tag_all) and tag_all != [] do
    from(
      activity in query,
      where: fragment("(? #> '{\"object\",\"tag\"}') \\?& ?", activity.data, ^tag_all)
    )
  end

  defp restrict_tag_all(query, _), do: query

462 463 464
  defp restrict_tag(query, %{"tag" => tag}) when is_list(tag) do
    from(
      activity in query,
465
      where: fragment("(? #> '{\"object\",\"tag\"}') \\?| ?", activity.data, ^tag)
Haelwenn's avatar
Haelwenn committed
466 467 468
    )
  end

469
  defp restrict_tag(query, %{"tag" => tag}) when is_binary(tag) do
lain's avatar
lain committed
470 471
    from(
      activity in query,
Roger Braun's avatar
Roger Braun committed
472
      where: fragment("? <@ (? #> '{\"object\",\"tag\"}')", ^tag, activity.data)
lain's avatar
lain committed
473
    )
Roger Braun's avatar
Roger Braun committed
474
  end
lain's avatar
lain committed
475

Roger Braun's avatar
Roger Braun committed
476 477
  defp restrict_tag(query, _), do: query

478 479 480 481 482
  defp restrict_to_cc(query, recipients_to, recipients_cc) do
    from(
      activity in query,
      where:
        fragment(
483 484
          "(?->'to' \\?| ?) or (?->'cc' \\?| ?)",
          activity.data,
485
          ^recipients_to,
486 487
          activity.data,
          ^recipients_cc
488 489 490 491
        )
    )
  end

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

lain's avatar
lain committed
494
  defp restrict_recipients(query, recipients, nil) do
lain's avatar
lain committed
495
    from(activity in query, where: fragment("? && ?", ^recipients, activity.recipients))
lain's avatar
lain committed
496
  end
lain's avatar
lain committed
497

lain's avatar
lain committed
498
  defp restrict_recipients(query, recipients, user) do
lain's avatar
lain committed
499 500
    from(
      activity in query,
lain's avatar
lain committed
501 502
      where: fragment("? && ?", ^recipients, activity.recipients),
      or_where: activity.actor == ^user.ap_id
lain's avatar
lain committed
503
    )
lain's avatar
lain committed
504
  end
lain's avatar
lain committed
505

kaniini's avatar
kaniini committed
506
  defp restrict_limit(query, %{"limit" => limit}) do
lain's avatar
lain committed
507
    from(activity in query, limit: ^limit)
kaniini's avatar
kaniini committed
508
  end
lain's avatar
lain committed
509

kaniini's avatar
kaniini committed
510 511
  defp restrict_limit(query, _), do: query

lain's avatar
lain committed
512
  defp restrict_local(query, %{"local_only" => true}) do
lain's avatar
lain committed
513
    from(activity in query, where: activity.local == true)
lain's avatar
lain committed
514
  end
lain's avatar
lain committed
515

lain's avatar
lain committed
516
  defp restrict_local(query, _), do: query
lain's avatar
lain committed
517

518 519
  defp restrict_max(query, %{"max_id" => ""}), do: query

lain's avatar
lain committed
520
  defp restrict_max(query, %{"max_id" => max_id}) do
lain's avatar
lain committed
521
    from(activity in query, where: activity.id < ^max_id)
522
  end
lain's avatar
lain committed
523

lain's avatar
lain committed
524
  defp restrict_max(query, _), do: query
525

lain's avatar
lain committed
526
  defp restrict_actor(query, %{"actor_id" => actor_id}) do
lain's avatar
lain committed
527
    from(activity in query, where: activity.actor == ^actor_id)
528
  end
lain's avatar
lain committed
529

lain's avatar
lain committed
530
  defp restrict_actor(query, _), do: query
531

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

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

540 541
  defp restrict_type(query, _), do: query

542
  defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
lain's avatar
lain committed
543 544
    from(
      activity in query,
545
      where: fragment("? <@ (? #> '{\"object\",\"likes\"}')", ^ap_id, activity.data)
lain's avatar
lain committed
546
    )
547
  end
lain's avatar
lain committed
548

549 550
  defp restrict_favorited_by(query, _), do: query

eal's avatar
eal committed
551
  defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
lain's avatar
lain committed
552 553
    from(
      activity in query,
eal's avatar
eal committed
554
      where: fragment("not (? #> '{\"object\",\"attachment\"}' = ?)", activity.data, ^[])
lain's avatar
lain committed
555
    )
eal's avatar
eal committed
556
  end
lain's avatar
lain committed
557

eal's avatar
eal committed
558 559
  defp restrict_media(query, _), do: query

560 561 562 563 564 565 566 567 568
  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

569 570 571 572 573 574
  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
575
  defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
lain's avatar
lain committed
576 577
    blocks = info.blocks || []
    domain_blocks = info.domain_blocks || []
lain's avatar
lain committed
578 579 580

    from(
      activity in query,
581
      where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
582
      where: fragment("not (?->'to' \\?| ?)", activity.data, ^blocks),
eal's avatar
eal committed
583
      where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks)
584
    )
585
  end
lain's avatar
lain committed
586

587 588
  defp restrict_blocked(query, _), do: query

589 590 591
  defp restrict_unlisted(query) do
    from(
      activity in query,
lain's avatar
Format.  
lain committed
592 593
      where:
        fragment(
lain's avatar
lain committed
594
          "not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)",
lain's avatar
Format.  
lain committed
595 596 597
          activity.data,
          ^["https://www.w3.org/ns/activitystreams#Public"]
        )
598 599 600
    )
  end

minibikini's avatar
minibikini committed
601 602 603 604 605 606
  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
607
  def fetch_activities_query(recipients, opts \\ %{}) do
lain's avatar
lain committed
608 609 610 611 612 613
    base_query =
      from(
        activity in Activity,
        limit: 20,
        order_by: [fragment("? desc nulls last", activity.id)]
      )
lain's avatar
lain committed
614

lain's avatar
lain committed
615
    base_query
lain's avatar
lain committed
616
    |> restrict_recipients(recipients, opts["user"])
Roger Braun's avatar
Roger Braun committed
617
    |> restrict_tag(opts)
Haelwenn's avatar
Haelwenn committed
618 619
    |> restrict_tag_reject(opts)
    |> restrict_tag_all(opts)
lain's avatar
lain committed
620 621
    |> restrict_since(opts)
    |> restrict_local(opts)
kaniini's avatar
kaniini committed
622
    |> restrict_limit(opts)
lain's avatar
lain committed
623 624
    |> restrict_max(opts)
    |> restrict_actor(opts)
625
    |> restrict_type(opts)
626
    |> restrict_favorited_by(opts)
627
    |> restrict_blocked(opts)
eal's avatar
eal committed
628
    |> restrict_media(opts)
629
    |> restrict_visibility(opts)
630
    |> restrict_replies(opts)
631
    |> restrict_reblogs(opts)
minibikini's avatar
minibikini committed
632
    |> restrict_pinned(opts)
lain's avatar
lain committed
633 634 635 636
  end

  def fetch_activities(recipients, opts \\ %{}) do
    fetch_activities_query(recipients, opts)
lain's avatar
lain committed
637 638
    |> Repo.all()
    |> Enum.reverse()
dtluna's avatar
dtluna committed
639 640
  end

641 642 643 644 645 646 647
  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
648 649
  def upload(file, opts \\ []) do
    with {:ok, data} <- Upload.store(file, opts) do
650 651 652 653 654 655 656
      obj_data =
        if opts[:actor] do
          Map.put(data, "actor", opts[:actor])
        else
          data
        end

657
      Repo.insert(%Object{data: obj_data})
658
    end
lain's avatar
lain committed
659
  end
lain's avatar
lain committed
660

lain's avatar
lain committed
661
  def user_data_from_user_object(data) do
lain's avatar
lain committed
662 663 664 665 666 667 668 669 670 671 672 673 674
    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
675

676
    locked = data["manuallyApprovesFollowers"] || false
677 678
    data = Transmogrifier.maybe_fix_user_object(data)

lain's avatar
lain committed
679 680 681 682 683
    user_data = %{
      ap_id: data["id"],
      info: %{
        "ap_enabled" => true,
        "source_data" => data,
684 685
        "banner" => banner,
        "locked" => locked
lain's avatar
lain committed
686 687 688 689 690 691 692
      },
      avatar: avatar,
      name: data["name"],
      follower_address: data["followers"],
      bio: data["summary"]
    }

693 694 695
    # nickname can be nil because of virtual actors
    user_data =
      if data["preferredUsername"] do
kaniini's avatar
kaniini committed
696 697 698 699 700
        Map.put(
          user_data,
          :nickname,
          "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}"
        )
701 702 703 704
      else
        Map.put(user_data, :nickname, nil)
      end

lain's avatar
lain committed
705 706 707
    {:ok, user_data}
  end

lain's avatar
lain committed
708
  def fetch_and_prepare_user_from_ap_id(ap_id) do
709
    with {:ok, data} <- fetch_and_contain_remote_object_from_id(ap_id) do
lain's avatar
lain committed
710
      user_data_from_user_object(data)
lain's avatar
lain committed
711
    else
712
      e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
lain's avatar
lain committed
713 714 715 716
    end
  end

  def make_user_from_ap_id(ap_id) do
feld's avatar
feld committed
717
    if _user = User.get_by_ap_id(ap_id) do
lain's avatar
lain committed
718 719 720 721
      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
722
      else
lain's avatar
lain committed
723
        e -> {:error, e}
lain's avatar
lain committed
724
      end
lain's avatar
lain committed
725 726
    end
  end
727

lain's avatar
lain committed
728 729 730
  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
731
    else
feld's avatar
feld committed
732
      _e -> {:error, "No AP id in WebFinger"}
lain's avatar
lain committed
733 734 735
    end
  end

736 737 738 739 740
  def should_federate?(inbox, public) do
    if public do
      true
    else
      inbox_info = URI.parse(inbox)
href's avatar
href committed
741
      !Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
742 743 744
    end
  end

745
  def publish(actor, activity) do
746
    remote_followers =
lain's avatar
lain committed
747 748 749 750 751 752
      if actor.follower_address in activity.recipients do
        {:ok, followers} = User.get_followers(actor)
        followers |> Enum.filter(&(!&1.local))
      else
        []
      end
753

754 755
    public = is_public?(activity)

756
    reachable_inboxes_metadata =
757
      (Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers)
lain's avatar
lain committed
758
      |> Enum.filter(fn user -> User.ap_enabled?(user) end)
lain's avatar
lain committed
759
      |> Enum.map(fn %{info: %{source_data: data}} ->
760
        (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
lain's avatar
lain committed
761 762
      end)
      |> Enum.uniq()
763
      |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
764
      |> Instances.filter_reachable()
765

lain's avatar
lain committed
766
    {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
lain's avatar
lain committed
767
    json = Jason.encode!(data)
lain's avatar
lain committed
768

769
    Enum.each(reachable_inboxes_metadata, fn {inbox, unreachable_since} ->
lain's avatar
lain committed
770 771 772 773
      Federator.enqueue(:publish_single_ap, %{
        inbox: inbox,
        json: json,
        actor: actor,
774 775
        id: activity.data["id"],
        unreachable_since: unreachable_since
lain's avatar
lain committed
776 777
      })
    end)
778
  end
779

780
  def publish_one(%{inbox: inbox, json: json, actor: actor, id: id} = params) do
lain's avatar
lain committed
781 782
    Logger.info("Federating #{id} to #{inbox}")
    host = URI.parse(inbox).host
lain's avatar
lain committed
783

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

lain's avatar
lain committed
786
    signature =
787 788 789 790 791
      Pleroma.Web.HTTPSignatures.sign(actor, %{
        host: host,
        "content-length": byte_size(json),
        digest: digest
      })
lain's avatar
lain committed
792

793
    with {:ok, %{status: code}} when code in 200..299 <-
794 795 796 797 798 799 800
           result =
             @httpoison.post(
               inbox,
               json,
               [
                 {"Content-Type", "application/activity+json"},
                 {"signature", signature},
801
                 {"digest", digest}
802 803
               ]
             ) do
804 805 806
      if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
        do: Instances.set_reachable(inbox)

807 808
      result
    else
809
      {_post_result, response} ->
810
        unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
811
        {:error, response}
812
    end
lain's avatar
lain committed
813 814
  end

815 816
  # TODO:
  # This will create a Create activity, which we need internally at the moment.
817 818 819 820
  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
821
      Logger.info("Fetching #{id} via AP")
lain's avatar
lain committed
822

823
      with {:ok, data} <- fetch_and_contain_remote_object_from_id(id),
824
           nil <- Object.normalize(data),
lain's avatar
lain committed
825 826 827 828
           params <- %{
             "type" => "Create",
             "to" => data["to"],
             "cc" => data["cc"],
829
             "actor" => data["actor"] || data["attributedTo"],
lain's avatar
lain committed
830 831
             "object" => data
           },
832
           :ok <- Transmogrifier.contain_origin(id, params),
833
           {:ok, activity} <- Transmogrifier.handle_incoming(params) do
834
        {:ok, Object.normalize(activity.data["object"])}
835
      else
836
        {:error, {:reject, nil}} ->
837 838
          {:reject, nil}

lain's avatar
lain committed
839 840 841
        object = %Object{} ->
          {:ok, object}

feld's avatar
feld committed
842
        _e ->
lain's avatar
lain committed