Merge remote-tracking branch 'origin/develop' into benchmark-finishing
[akkoma] / lib / pleroma / web / ostatus / ostatus_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.OStatus.OStatusController do
6 use Pleroma.Web, :controller
7
8 alias Fallback.RedirectController
9 alias Pleroma.Activity
10 alias Pleroma.Object
11 alias Pleroma.User
12 alias Pleroma.Web.ActivityPub.ActivityPubController
13 alias Pleroma.Web.ActivityPub.ObjectView
14 alias Pleroma.Web.ActivityPub.Visibility
15 alias Pleroma.Web.Endpoint
16 alias Pleroma.Web.Federator
17 alias Pleroma.Web.Metadata.PlayerView
18 alias Pleroma.Web.OStatus.ActivityRepresenter
19 alias Pleroma.Web.Router
20 alias Pleroma.Web.XML
21
22 plug(
23 Pleroma.Plugs.RateLimiter,
24 {:ap_routes, params: ["uuid"]} when action in [:object, :activity]
25 )
26
27 plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])
28
29 plug(
30 Pleroma.Plugs.SetFormatPlug
31 when action in [:object, :activity, :notice]
32 )
33
34 action_fallback(:errors)
35
36 defp decode_or_retry(body) do
37 with {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
38 {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
39 {:ok, doc}
40 else
41 _e ->
42 with [decoded | _] <- Pleroma.Web.Salmon.decode(body),
43 doc <- XML.parse_document(decoded),
44 uri when not is_nil(uri) <- XML.string_from_xpath("/entry/author[1]/uri", doc),
45 {:ok, _} <- Pleroma.Web.OStatus.make_user(uri, true),
46 {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
47 {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
48 {:ok, doc}
49 end
50 end
51 end
52
53 def salmon_incoming(conn, _) do
54 {:ok, body, _conn} = read_body(conn)
55 {:ok, doc} = decode_or_retry(body)
56
57 Federator.incoming_doc(doc)
58
59 send_resp(conn, 200, "")
60 end
61
62 def object(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
63 when format in ["json", "activity+json"] do
64 ActivityPubController.call(conn, :object)
65 end
66
67 def object(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
68 with id <- o_status_url(conn, :object, uuid),
69 {_, %Activity{} = activity} <-
70 {:activity, Activity.get_create_by_object_ap_id_with_object(id)},
71 {_, true} <- {:public?, Visibility.is_public?(activity)},
72 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
73 case format do
74 "html" -> redirect(conn, to: "/notice/#{activity.id}")
75 _ -> represent_activity(conn, nil, activity, user)
76 end
77 else
78 reason when reason in [{:public?, false}, {:activity, nil}] ->
79 {:error, :not_found}
80
81 e ->
82 e
83 end
84 end
85
86 def activity(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
87 when format in ["json", "activity+json"] do
88 ActivityPubController.call(conn, :activity)
89 end
90
91 def activity(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
92 with id <- o_status_url(conn, :activity, uuid),
93 {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
94 {_, true} <- {:public?, Visibility.is_public?(activity)},
95 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
96 case format do
97 "html" -> redirect(conn, to: "/notice/#{activity.id}")
98 _ -> represent_activity(conn, format, activity, user)
99 end
100 else
101 reason when reason in [{:public?, false}, {:activity, nil}] ->
102 {:error, :not_found}
103
104 e ->
105 e
106 end
107 end
108
109 def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do
110 with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)},
111 {_, true} <- {:public?, Visibility.is_public?(activity)},
112 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
113 cond do
114 format == "html" && activity.data["type"] == "Create" ->
115 %Object{} = object = Object.normalize(activity)
116
117 RedirectController.redirector_with_meta(
118 conn,
119 %{
120 activity_id: activity.id,
121 object: object,
122 url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id),
123 user: user
124 }
125 )
126
127 format == "html" ->
128 RedirectController.redirector(conn, nil)
129
130 true ->
131 represent_activity(conn, format, activity, user)
132 end
133 else
134 reason when reason in [{:public?, false}, {:activity, nil}] ->
135 conn
136 |> put_status(404)
137 |> RedirectController.redirector(nil, 404)
138
139 e ->
140 e
141 end
142 end
143
144 # Returns an HTML embedded <audio> or <video> player suitable for embed iframes.
145 def notice_player(conn, %{"id" => id}) do
146 with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id_with_object(id),
147 true <- Visibility.is_public?(activity),
148 %Object{} = object <- Object.normalize(activity),
149 %{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object,
150 true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do
151 conn
152 |> put_layout(:metadata_player)
153 |> put_resp_header("x-frame-options", "ALLOW")
154 |> put_resp_header(
155 "content-security-policy",
156 "default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
157 )
158 |> put_view(PlayerView)
159 |> render("player.html", url)
160 else
161 _error ->
162 conn
163 |> put_status(404)
164 |> RedirectController.redirector(nil, 404)
165 end
166 end
167
168 defp represent_activity(
169 conn,
170 "activity+json",
171 %Activity{data: %{"type" => "Create"}} = activity,
172 _user
173 ) do
174 object = Object.normalize(activity)
175
176 conn
177 |> put_resp_header("content-type", "application/activity+json")
178 |> put_view(ObjectView)
179 |> render("object.json", %{object: object})
180 end
181
182 defp represent_activity(_conn, "activity+json", _, _) do
183 {:error, :not_found}
184 end
185
186 defp represent_activity(conn, _, activity, user) do
187 response =
188 activity
189 |> ActivityRepresenter.to_simple_form(user, true)
190 |> ActivityRepresenter.wrap_with_entry()
191 |> :xmerl.export_simple(:xmerl_xml)
192 |> to_string
193
194 conn
195 |> put_resp_content_type("application/atom+xml")
196 |> send_resp(200, response)
197 end
198
199 def errors(conn, {:error, :not_found}) do
200 render_error(conn, :not_found, "Not found")
201 end
202
203 def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
204
205 def errors(conn, _) do
206 render_error(conn, :internal_server_error, "Something went wrong")
207 end
208 end