Merge branch 'develop' into feature/digest-email
[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(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])
26
27 plug(
28 Pleroma.Plugs.SetFormatPlug
29 when action in [:feed_redirect, :object, :activity, :notice]
30 )
31
32 action_fallback(:errors)
33
34 def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
35 with {_, %User{} = user} <-
36 {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
37 RedirectController.redirector_with_meta(conn, %{user: user})
38 end
39 end
40
41 def feed_redirect(%{assigns: %{format: format}} = conn, _params)
42 when format in ["json", "activity+json"] do
43 ActivityPubController.call(conn, :user)
44 end
45
46 def feed_redirect(conn, %{"nickname" => nickname}) do
47 with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
48 redirect(conn, external: OStatus.feed_path(user))
49 end
50 end
51
52 def feed(conn, %{"nickname" => nickname} = params) do
53 with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
54 query_params =
55 Map.take(params, ["max_id"])
56 |> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
57
58 activities =
59 ActivityPub.fetch_public_activities(query_params)
60 |> Enum.reverse()
61
62 response =
63 user
64 |> FeedRepresenter.to_simple_form(activities, [user])
65 |> :xmerl.export_simple(:xmerl_xml)
66 |> to_string
67
68 conn
69 |> put_resp_content_type("application/atom+xml")
70 |> send_resp(200, response)
71 end
72 end
73
74 defp decode_or_retry(body) do
75 with {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
76 {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
77 {:ok, doc}
78 else
79 _e ->
80 with [decoded | _] <- Pleroma.Web.Salmon.decode(body),
81 doc <- XML.parse_document(decoded),
82 uri when not is_nil(uri) <- XML.string_from_xpath("/entry/author[1]/uri", doc),
83 {:ok, _} <- Pleroma.Web.OStatus.make_user(uri, true),
84 {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
85 {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
86 {:ok, doc}
87 end
88 end
89 end
90
91 def salmon_incoming(conn, _) do
92 {:ok, body, _conn} = read_body(conn)
93 {:ok, doc} = decode_or_retry(body)
94
95 Federator.incoming_doc(doc)
96
97 conn
98 |> send_resp(200, "")
99 end
100
101 def object(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
102 when format in ["json", "activity+json"] do
103 ActivityPubController.call(conn, :object)
104 end
105
106 def object(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
107 with id <- o_status_url(conn, :object, uuid),
108 {_, %Activity{} = activity} <-
109 {:activity, Activity.get_create_by_object_ap_id_with_object(id)},
110 {_, true} <- {:public?, Visibility.is_public?(activity)},
111 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
112 case format do
113 "html" -> redirect(conn, to: "/notice/#{activity.id}")
114 _ -> represent_activity(conn, nil, activity, user)
115 end
116 else
117 reason when reason in [{:public?, false}, {:activity, nil}] ->
118 {:error, :not_found}
119
120 e ->
121 e
122 end
123 end
124
125 def activity(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
126 when format in ["json", "activity+json"] do
127 ActivityPubController.call(conn, :activity)
128 end
129
130 def activity(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
131 with id <- o_status_url(conn, :activity, uuid),
132 {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
133 {_, true} <- {:public?, Visibility.is_public?(activity)},
134 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
135 case format do
136 "html" -> redirect(conn, to: "/notice/#{activity.id}")
137 _ -> represent_activity(conn, format, activity, user)
138 end
139 else
140 reason when reason in [{:public?, false}, {:activity, nil}] ->
141 {:error, :not_found}
142
143 e ->
144 e
145 end
146 end
147
148 def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do
149 with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)},
150 {_, true} <- {:public?, Visibility.is_public?(activity)},
151 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
152 cond do
153 format == "html" && activity.data["type"] == "Create" ->
154 %Object{} = object = Object.normalize(activity)
155
156 RedirectController.redirector_with_meta(
157 conn,
158 %{
159 activity_id: activity.id,
160 object: object,
161 url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id),
162 user: user
163 }
164 )
165
166 format == "html" ->
167 RedirectController.redirector(conn, nil)
168
169 true ->
170 represent_activity(conn, format, activity, user)
171 end
172 else
173 reason when reason in [{:public?, false}, {:activity, nil}] ->
174 conn
175 |> put_status(404)
176 |> RedirectController.redirector(nil, 404)
177
178 e ->
179 e
180 end
181 end
182
183 # Returns an HTML embedded <audio> or <video> player suitable for embed iframes.
184 def notice_player(conn, %{"id" => id}) do
185 with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id_with_object(id),
186 true <- Visibility.is_public?(activity),
187 %Object{} = object <- Object.normalize(activity),
188 %{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object,
189 true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do
190 conn
191 |> put_layout(:metadata_player)
192 |> put_resp_header("x-frame-options", "ALLOW")
193 |> put_resp_header(
194 "content-security-policy",
195 "default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
196 )
197 |> put_view(PlayerView)
198 |> render("player.html", url)
199 else
200 _error ->
201 conn
202 |> put_status(404)
203 |> RedirectController.redirector(nil, 404)
204 end
205 end
206
207 defp represent_activity(
208 conn,
209 "activity+json",
210 %Activity{data: %{"type" => "Create"}} = activity,
211 _user
212 ) do
213 object = Object.normalize(activity)
214
215 conn
216 |> put_resp_header("content-type", "application/activity+json")
217 |> json(ObjectView.render("object.json", %{object: object}))
218 end
219
220 defp represent_activity(_conn, "activity+json", _, _) do
221 {:error, :not_found}
222 end
223
224 defp represent_activity(conn, _, activity, user) do
225 response =
226 activity
227 |> ActivityRepresenter.to_simple_form(user, true)
228 |> ActivityRepresenter.wrap_with_entry()
229 |> :xmerl.export_simple(:xmerl_xml)
230 |> to_string
231
232 conn
233 |> put_resp_content_type("application/atom+xml")
234 |> send_resp(200, response)
235 end
236
237 def errors(conn, {:error, :not_found}) do
238 render_error(conn, :not_found, "Not found")
239 end
240
241 def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
242
243 def errors(conn, _) do
244 render_error(conn, :internal_server_error, "Something went wrong")
245 end
246 end