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} <-
41 {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
42 RedirectController.redirector_with_meta(conn, %{user: user})
46 def feed_redirect(%{assigns: %{format: format}} = conn, _params)
47 when format in ["json", "activity+json"] do
48 ActivityPubController.call(conn, :user)
51 def feed_redirect(conn, %{"nickname" => nickname}) do
52 with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
53 redirect(conn, external: OStatus.feed_path(user))
57 def feed(conn, %{"nickname" => nickname} = params) do
58 with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
60 Map.take(params, ["max_id"])
61 |> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
64 ActivityPub.fetch_public_activities(query_params)
69 |> FeedRepresenter.to_simple_form(activities, [user])
70 |> :xmerl.export_simple(:xmerl_xml)
74 |> put_resp_content_type("application/atom+xml")
75 |> send_resp(200, response)
79 defp decode_or_retry(body) do
80 with {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
81 {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
85 with [decoded | _] <- Pleroma.Web.Salmon.decode(body),
86 doc <- XML.parse_document(decoded),
87 uri when not is_nil(uri) <- XML.string_from_xpath("/entry/author[1]/uri", doc),
88 {:ok, _} <- Pleroma.Web.OStatus.make_user(uri, true),
89 {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
90 {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
96 def salmon_incoming(conn, _) do
97 {:ok, body, _conn} = read_body(conn)
98 {:ok, doc} = decode_or_retry(body)
100 Federator.incoming_doc(doc)
103 |> send_resp(200, "")
106 def object(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
107 when format in ["json", "activity+json"] do
108 ActivityPubController.call(conn, :object)
111 def object(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
112 with id <- o_status_url(conn, :object, uuid),
113 {_, %Activity{} = activity} <-
114 {:activity, Activity.get_create_by_object_ap_id_with_object(id)},
115 {_, true} <- {:public?, Visibility.is_public?(activity)},
116 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
118 "html" -> redirect(conn, to: "/notice/#{activity.id}")
119 _ -> represent_activity(conn, nil, activity, user)
122 reason when reason in [{:public?, false}, {:activity, nil}] ->
130 def activity(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
131 when format in ["json", "activity+json"] do
132 ActivityPubController.call(conn, :activity)
135 def activity(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
136 with id <- o_status_url(conn, :activity, uuid),
137 {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
138 {_, true} <- {:public?, Visibility.is_public?(activity)},
139 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
141 "html" -> redirect(conn, to: "/notice/#{activity.id}")
142 _ -> represent_activity(conn, format, activity, user)
145 reason when reason in [{:public?, false}, {:activity, nil}] ->
153 def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do
154 with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)},
155 {_, true} <- {:public?, Visibility.is_public?(activity)},
156 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
158 format == "html" && activity.data["type"] == "Create" ->
159 %Object{} = object = Object.normalize(activity)
161 RedirectController.redirector_with_meta(
164 activity_id: activity.id,
166 url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id),
172 RedirectController.redirector(conn, nil)
175 represent_activity(conn, format, activity, user)
178 reason when reason in [{:public?, false}, {:activity, nil}] ->
181 |> RedirectController.redirector(nil, 404)
188 # Returns an HTML embedded <audio> or <video> player suitable for embed iframes.
189 def notice_player(conn, %{"id" => id}) do
190 with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id_with_object(id),
191 true <- Visibility.is_public?(activity),
192 %Object{} = object <- Object.normalize(activity),
193 %{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object,
194 true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do
196 |> put_layout(:metadata_player)
197 |> put_resp_header("x-frame-options", "ALLOW")
199 "content-security-policy",
200 "default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
202 |> put_view(PlayerView)
203 |> render("player.html", url)
208 |> RedirectController.redirector(nil, 404)
212 defp represent_activity(
215 %Activity{data: %{"type" => "Create"}} = activity,
218 object = Object.normalize(activity)
221 |> put_resp_header("content-type", "application/activity+json")
222 |> json(ObjectView.render("object.json", %{object: object}))
225 defp represent_activity(_conn, "activity+json", _, _) do
229 defp represent_activity(conn, _, activity, user) do
232 |> ActivityRepresenter.to_simple_form(user, true)
233 |> ActivityRepresenter.wrap_with_entry()
234 |> :xmerl.export_simple(:xmerl_xml)
238 |> put_resp_content_type("application/atom+xml")
239 |> send_resp(200, response)
242 def errors(conn, {:error, :not_found}) do
243 render_error(conn, :not_found, "Not found")
246 def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
248 def errors(conn, _) do
249 render_error(conn, :internal_server_error, "Something went wrong")