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
59 Map.take(params, ["max_id"])
60 |> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
63 ActivityPub.fetch_public_activities(query_params)
68 |> FeedRepresenter.to_simple_form(activities, [user])
69 |> :xmerl.export_simple(:xmerl_xml)
73 |> put_resp_content_type("application/atom+xml")
74 |> send_resp(200, response)
78 defp decode_or_retry(body) do
79 with {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
80 {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
84 with [decoded | _] <- Pleroma.Web.Salmon.decode(body),
85 doc <- XML.parse_document(decoded),
86 uri when not is_nil(uri) <- XML.string_from_xpath("/entry/author[1]/uri", doc),
87 {:ok, _} <- Pleroma.Web.OStatus.make_user(uri, true),
88 {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
89 {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
95 def salmon_incoming(conn, _) do
96 {:ok, body, _conn} = read_body(conn)
97 {:ok, doc} = decode_or_retry(body)
99 Federator.incoming_doc(doc)
102 |> send_resp(200, "")
105 def object(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
106 when format in ["json", "activity+json"] do
107 ActivityPubController.call(conn, :object)
110 def object(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
111 with id <- o_status_url(conn, :object, uuid),
112 {_, %Activity{} = activity} <-
113 {:activity, Activity.get_create_by_object_ap_id_with_object(id)},
114 {_, true} <- {:public?, Visibility.is_public?(activity)},
115 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
117 "html" -> redirect(conn, to: "/notice/#{activity.id}")
118 _ -> represent_activity(conn, nil, activity, user)
121 reason when reason in [{:public?, false}, {:activity, nil}] ->
129 def activity(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
130 when format in ["json", "activity+json"] do
131 ActivityPubController.call(conn, :activity)
134 def activity(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
135 with id <- o_status_url(conn, :activity, uuid),
136 {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
137 {_, true} <- {:public?, Visibility.is_public?(activity)},
138 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
140 "html" -> redirect(conn, to: "/notice/#{activity.id}")
141 _ -> represent_activity(conn, format, activity, user)
144 reason when reason in [{:public?, false}, {:activity, nil}] ->
152 def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do
153 with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)},
154 {_, true} <- {:public?, Visibility.is_public?(activity)},
155 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
157 format == "html" && activity.data["type"] == "Create" ->
158 %Object{} = object = Object.normalize(activity)
160 RedirectController.redirector_with_meta(
163 activity_id: activity.id,
165 url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id),
171 RedirectController.redirector(conn, nil)
174 represent_activity(conn, format, activity, user)
177 reason when reason in [{:public?, false}, {:activity, nil}] ->
180 |> RedirectController.redirector(nil, 404)
187 # Returns an HTML embedded <audio> or <video> player suitable for embed iframes.
188 def notice_player(conn, %{"id" => id}) do
189 with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id_with_object(id),
190 true <- Visibility.is_public?(activity),
191 %Object{} = object <- Object.normalize(activity),
192 %{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object,
193 true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do
195 |> put_layout(:metadata_player)
196 |> put_resp_header("x-frame-options", "ALLOW")
198 "content-security-policy",
199 "default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
201 |> put_view(PlayerView)
202 |> render("player.html", url)
207 |> RedirectController.redirector(nil, 404)
211 defp represent_activity(
214 %Activity{data: %{"type" => "Create"}} = activity,
217 object = Object.normalize(activity)
220 |> put_resp_header("content-type", "application/activity+json")
221 |> json(ObjectView.render("object.json", %{object: object}))
224 defp represent_activity(_conn, "activity+json", _, _) do
228 defp represent_activity(conn, _, activity, user) do
231 |> ActivityRepresenter.to_simple_form(user, true)
232 |> ActivityRepresenter.wrap_with_entry()
233 |> :xmerl.export_simple(:xmerl_xml)
237 |> put_resp_content_type("application/atom+xml")
238 |> send_resp(200, response)
241 def errors(conn, {:error, :not_found}) do
242 render_error(conn, :not_found, "Not found")
245 def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
247 def errors(conn, _) do
248 render_error(conn, :internal_server_error, "Something went wrong")