1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.OStatus.OStatusController do
6 use Pleroma.Web, :controller
8 alias Fallback.RedirectController
12 alias Pleroma.Web.ActivityPub.ActivityPub
13 alias Pleroma.Web.ActivityPub.ActivityPubController
14 alias Pleroma.Web.ActivityPub.ObjectView
15 alias Pleroma.Web.ActivityPub.Visibility
16 alias Pleroma.Web.Endpoint
17 alias Pleroma.Web.Federator
18 alias Pleroma.Web.Metadata.PlayerView
19 alias Pleroma.Web.OStatus
20 alias Pleroma.Web.OStatus.ActivityRepresenter
21 alias Pleroma.Web.OStatus.FeedRepresenter
22 alias Pleroma.Web.Router
26 Pleroma.Plugs.RateLimiter,
27 {:ap_routes, params: ["uuid"]} when action in [:object, :activity]
30 plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])
33 Pleroma.Plugs.SetFormatPlug
34 when action in [:feed_redirect, :object, :activity, :notice]
37 action_fallback(:errors)
39 def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
40 with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
41 RedirectController.redirector_with_meta(conn, %{user: user})
45 def feed_redirect(%{assigns: %{format: format}} = conn, _params)
46 when format in ["json", "activity+json"] do
47 ActivityPubController.call(conn, :user)
50 def feed_redirect(conn, %{"nickname" => nickname}) do
51 with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
52 redirect(conn, external: OStatus.feed_path(user))
56 def feed(conn, %{"nickname" => nickname} = params) do
57 with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
60 |> Map.take(["max_id"])
61 |> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
62 |> ActivityPub.fetch_public_activities()
67 |> FeedRepresenter.to_simple_form(activities, [user])
68 |> :xmerl.export_simple(:xmerl_xml)
72 |> put_resp_content_type("application/atom+xml")
73 |> send_resp(200, response)
77 defp decode_or_retry(body) do
78 with {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
79 {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
83 with [decoded | _] <- Pleroma.Web.Salmon.decode(body),
84 doc <- XML.parse_document(decoded),
85 uri when not is_nil(uri) <- XML.string_from_xpath("/entry/author[1]/uri", doc),
86 {:ok, _} <- Pleroma.Web.OStatus.make_user(uri, true),
87 {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
88 {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
94 def salmon_incoming(conn, _) do
95 {:ok, body, _conn} = read_body(conn)
96 {:ok, doc} = decode_or_retry(body)
98 Federator.incoming_doc(doc)
100 send_resp(conn, 200, "")
103 def object(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
104 when format in ["json", "activity+json"] do
105 ActivityPubController.call(conn, :object)
108 def object(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
109 with id <- o_status_url(conn, :object, uuid),
110 {_, %Activity{} = activity} <-
111 {:activity, Activity.get_create_by_object_ap_id_with_object(id)},
112 {_, true} <- {:public?, Visibility.is_public?(activity)},
113 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
115 "html" -> redirect(conn, to: "/notice/#{activity.id}")
116 _ -> represent_activity(conn, nil, activity, user)
119 reason when reason in [{:public?, false}, {:activity, nil}] ->
127 def activity(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
128 when format in ["json", "activity+json"] do
129 ActivityPubController.call(conn, :activity)
132 def activity(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
133 with id <- o_status_url(conn, :activity, uuid),
134 {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
135 {_, true} <- {:public?, Visibility.is_public?(activity)},
136 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
138 "html" -> redirect(conn, to: "/notice/#{activity.id}")
139 _ -> represent_activity(conn, format, activity, user)
142 reason when reason in [{:public?, false}, {:activity, nil}] ->
150 def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do
151 with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)},
152 {_, true} <- {:public?, Visibility.is_public?(activity)},
153 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
155 format == "html" && activity.data["type"] == "Create" ->
156 %Object{} = object = Object.normalize(activity)
158 RedirectController.redirector_with_meta(
161 activity_id: activity.id,
163 url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id),
169 RedirectController.redirector(conn, nil)
172 represent_activity(conn, format, activity, user)
175 reason when reason in [{:public?, false}, {:activity, nil}] ->
178 |> RedirectController.redirector(nil, 404)
185 # Returns an HTML embedded <audio> or <video> player suitable for embed iframes.
186 def notice_player(conn, %{"id" => id}) do
187 with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id_with_object(id),
188 true <- Visibility.is_public?(activity),
189 %Object{} = object <- Object.normalize(activity),
190 %{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object,
191 true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do
193 |> put_layout(:metadata_player)
194 |> put_resp_header("x-frame-options", "ALLOW")
196 "content-security-policy",
197 "default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
199 |> put_view(PlayerView)
200 |> render("player.html", url)
205 |> RedirectController.redirector(nil, 404)
209 defp represent_activity(
212 %Activity{data: %{"type" => "Create"}} = activity,
215 object = Object.normalize(activity)
218 |> put_resp_header("content-type", "application/activity+json")
219 |> json(ObjectView.render("object.json", %{object: object}))
222 defp represent_activity(_conn, "activity+json", _, _) do
226 defp represent_activity(conn, _, activity, user) do
229 |> ActivityRepresenter.to_simple_form(user, true)
230 |> ActivityRepresenter.wrap_with_entry()
231 |> :xmerl.export_simple(:xmerl_xml)
235 |> put_resp_content_type("application/atom+xml")
236 |> send_resp(200, response)
239 def errors(conn, {:error, :not_found}) do
240 render_error(conn, :not_found, "Not found")
243 def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
245 def errors(conn, _) do
246 render_error(conn, :internal_server_error, "Something went wrong")