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