eva
[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.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
23 alias Pleroma.Web.XML
24
25 plug(
26 Pleroma.Plugs.RateLimiter,
27 {:ap_routes, params: ["uuid"]} when action in [:object, :activity]
28 )
29
30 plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])
31
32 plug(
33 Pleroma.Plugs.SetFormatPlug
34 when action in [:feed_redirect, :object, :activity, :notice]
35 )
36
37 action_fallback(:errors)
38
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})
42 end
43 end
44
45 def feed_redirect(%{assigns: %{format: format}} = conn, _params)
46 when format in ["json", "activity+json"] do
47 ActivityPubController.call(conn, :user)
48 end
49
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))
53 end
54 end
55
56 def feed(conn, %{"nickname" => nickname} = params) do
57 with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
58 query_params =
59 Map.take(params, ["max_id"])
60 |> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
61
62 activities =
63 ActivityPub.fetch_public_activities(query_params)
64 |> Enum.reverse()
65
66 response =
67 user
68 |> FeedRepresenter.to_simple_form(activities, [user])
69 |> :xmerl.export_simple(:xmerl_xml)
70 |> to_string
71
72 conn
73 |> put_resp_content_type("application/atom+xml")
74 |> send_resp(200, response)
75 end
76 end
77
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
81 {:ok, doc}
82 else
83 _e ->
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
90 {:ok, doc}
91 end
92 end
93 end
94
95 def salmon_incoming(conn, _) do
96 {:ok, body, _conn} = read_body(conn)
97 {:ok, doc} = decode_or_retry(body)
98
99 Federator.incoming_doc(doc)
100
101 conn
102 |> send_resp(200, "")
103 end
104
105 def object(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
106 when format in ["json", "activity+json"] do
107 ActivityPubController.call(conn, :object)
108 end
109
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
116 case format do
117 "html" -> redirect(conn, to: "/notice/#{activity.id}")
118 _ -> represent_activity(conn, nil, activity, user)
119 end
120 else
121 reason when reason in [{:public?, false}, {:activity, nil}] ->
122 {:error, :not_found}
123
124 e ->
125 e
126 end
127 end
128
129 def activity(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
130 when format in ["json", "activity+json"] do
131 ActivityPubController.call(conn, :activity)
132 end
133
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
139 case format do
140 "html" -> redirect(conn, to: "/notice/#{activity.id}")
141 _ -> represent_activity(conn, format, activity, user)
142 end
143 else
144 reason when reason in [{:public?, false}, {:activity, nil}] ->
145 {:error, :not_found}
146
147 e ->
148 e
149 end
150 end
151
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
156 cond do
157 format == "html" && activity.data["type"] == "Create" ->
158 %Object{} = object = Object.normalize(activity)
159
160 RedirectController.redirector_with_meta(
161 conn,
162 %{
163 activity_id: activity.id,
164 object: object,
165 url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id),
166 user: user
167 }
168 )
169
170 format == "html" ->
171 RedirectController.redirector(conn, nil)
172
173 true ->
174 represent_activity(conn, format, activity, user)
175 end
176 else
177 reason when reason in [{:public?, false}, {:activity, nil}] ->
178 conn
179 |> put_status(404)
180 |> RedirectController.redirector(nil, 404)
181
182 e ->
183 e
184 end
185 end
186
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
194 conn
195 |> put_layout(:metadata_player)
196 |> put_resp_header("x-frame-options", "ALLOW")
197 |> put_resp_header(
198 "content-security-policy",
199 "default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
200 )
201 |> put_view(PlayerView)
202 |> render("player.html", url)
203 else
204 _error ->
205 conn
206 |> put_status(404)
207 |> RedirectController.redirector(nil, 404)
208 end
209 end
210
211 defp represent_activity(
212 conn,
213 "activity+json",
214 %Activity{data: %{"type" => "Create"}} = activity,
215 _user
216 ) do
217 object = Object.normalize(activity)
218
219 conn
220 |> put_resp_header("content-type", "application/activity+json")
221 |> json(ObjectView.render("object.json", %{object: object}))
222 end
223
224 defp represent_activity(_conn, "activity+json", _, _) do
225 {:error, :not_found}
226 end
227
228 defp represent_activity(conn, _, activity, user) do
229 response =
230 activity
231 |> ActivityRepresenter.to_simple_form(user, true)
232 |> ActivityRepresenter.wrap_with_entry()
233 |> :xmerl.export_simple(:xmerl_xml)
234 |> to_string
235
236 conn
237 |> put_resp_content_type("application/atom+xml")
238 |> send_resp(200, response)
239 end
240
241 def errors(conn, {:error, :not_found}) do
242 render_error(conn, :not_found, "Not found")
243 end
244
245 def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
246
247 def errors(conn, _) do
248 render_error(conn, :internal_server_error, "Something went wrong")
249 end
250 end