Merge branch 'test/activity_pub/transmogrifier.ex' into 'develop'
[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 activities =
59 params
60 |> Map.take(["max_id"])
61 |> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
62 |> ActivityPub.fetch_public_activities()
63 |> Enum.reverse()
64
65 response =
66 user
67 |> FeedRepresenter.to_simple_form(activities, [user])
68 |> :xmerl.export_simple(:xmerl_xml)
69 |> to_string
70
71 conn
72 |> put_resp_content_type("application/atom+xml")
73 |> send_resp(200, response)
74 end
75 end
76
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
80 {:ok, doc}
81 else
82 _e ->
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
89 {:ok, doc}
90 end
91 end
92 end
93
94 def salmon_incoming(conn, _) do
95 {:ok, body, _conn} = read_body(conn)
96 {:ok, doc} = decode_or_retry(body)
97
98 Federator.incoming_doc(doc)
99
100 send_resp(conn, 200, "")
101 end
102
103 def object(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
104 when format in ["json", "activity+json"] do
105 ActivityPubController.call(conn, :object)
106 end
107
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
114 case format do
115 "html" -> redirect(conn, to: "/notice/#{activity.id}")
116 _ -> represent_activity(conn, nil, activity, user)
117 end
118 else
119 reason when reason in [{:public?, false}, {:activity, nil}] ->
120 {:error, :not_found}
121
122 e ->
123 e
124 end
125 end
126
127 def activity(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
128 when format in ["json", "activity+json"] do
129 ActivityPubController.call(conn, :activity)
130 end
131
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
137 case format do
138 "html" -> redirect(conn, to: "/notice/#{activity.id}")
139 _ -> represent_activity(conn, format, activity, user)
140 end
141 else
142 reason when reason in [{:public?, false}, {:activity, nil}] ->
143 {:error, :not_found}
144
145 e ->
146 e
147 end
148 end
149
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
154 cond do
155 format == "html" && activity.data["type"] == "Create" ->
156 %Object{} = object = Object.normalize(activity)
157
158 RedirectController.redirector_with_meta(
159 conn,
160 %{
161 activity_id: activity.id,
162 object: object,
163 url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id),
164 user: user
165 }
166 )
167
168 format == "html" ->
169 RedirectController.redirector(conn, nil)
170
171 true ->
172 represent_activity(conn, format, activity, user)
173 end
174 else
175 reason when reason in [{:public?, false}, {:activity, nil}] ->
176 conn
177 |> put_status(404)
178 |> RedirectController.redirector(nil, 404)
179
180 e ->
181 e
182 end
183 end
184
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
192 conn
193 |> put_layout(:metadata_player)
194 |> put_resp_header("x-frame-options", "ALLOW")
195 |> put_resp_header(
196 "content-security-policy",
197 "default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
198 )
199 |> put_view(PlayerView)
200 |> render("player.html", url)
201 else
202 _error ->
203 conn
204 |> put_status(404)
205 |> RedirectController.redirector(nil, 404)
206 end
207 end
208
209 defp represent_activity(
210 conn,
211 "activity+json",
212 %Activity{data: %{"type" => "Create"}} = activity,
213 _user
214 ) do
215 object = Object.normalize(activity)
216
217 conn
218 |> put_resp_header("content-type", "application/activity+json")
219 |> put_view(ObjectView)
220 |> render("object.json", %{object: object})
221 end
222
223 defp represent_activity(_conn, "activity+json", _, _) do
224 {:error, :not_found}
225 end
226
227 defp represent_activity(conn, _, activity, user) do
228 response =
229 activity
230 |> ActivityRepresenter.to_simple_form(user, true)
231 |> ActivityRepresenter.wrap_with_entry()
232 |> :xmerl.export_simple(:xmerl_xml)
233 |> to_string
234
235 conn
236 |> put_resp_content_type("application/atom+xml")
237 |> send_resp(200, response)
238 end
239
240 def errors(conn, {:error, :not_found}) do
241 render_error(conn, :not_found, "Not found")
242 end
243
244 def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
245
246 def errors(conn, _) do
247 render_error(conn, :internal_server_error, "Something went wrong")
248 end
249 end