Commit fa4ec17c authored by lain's avatar lain

Merge branch '1560-non-federating-instances-routes-restrictions' into 'develop'

[#1560] Restricted AP- & OStatus-related routes for non-federating instances

Closes #1560

See merge request pleroma/pleroma!2235
parents d84670b9 ecb7809e
...@@ -180,7 +180,7 @@ Post here request with grant_type=refresh_token to obtain new access token. Retu ...@@ -180,7 +180,7 @@ Post here request with grant_type=refresh_token to obtain new access token. Retu
## Account Registration ## Account Registration
`POST /api/v1/accounts` `POST /api/v1/accounts`
Has theses additionnal parameters (which are the same as in Pleroma-API): Has theses additional parameters (which are the same as in Pleroma-API):
* `fullname`: optional * `fullname`: optional
* `bio`: optional * `bio`: optional
* `captcha_solution`: optional, contains provider-specific captcha solution, * `captcha_solution`: optional, contains provider-specific captcha solution,
......
# Pleroma Clients # Pleroma Clients
Note: Additionnal clients may be working but theses are officially supporting Pleroma. Note: Additional clients may be working but theses are officially supporting Pleroma.
Feel free to contact us to be added to this list! Feel free to contact us to be added to this list!
## Desktop ## Desktop
......
...@@ -15,9 +15,24 @@ def call(%{assigns: %{user: %User{}}} = conn, _) do ...@@ -15,9 +15,24 @@ def call(%{assigns: %{user: %User{}}} = conn, _) do
conn conn
end end
def call(conn, _) do def call(conn, options) do
perform =
cond do
options[:if_func] -> options[:if_func].()
options[:unless_func] -> !options[:unless_func].()
true -> true
end
if perform do
fail(conn)
else
conn
end
end
def fail(conn) do
conn conn
|> render_error(:forbidden, "Invalid credentials.") |> render_error(:forbidden, "Invalid credentials.")
|> halt |> halt()
end end
end end
...@@ -10,14 +10,20 @@ def init(options) do ...@@ -10,14 +10,20 @@ def init(options) do
end end
def call(conn, _opts) do def call(conn, _opts) do
if Pleroma.Config.get([:instance, :federating]) do if federating?() do
conn conn
else else
conn fail(conn)
|> put_status(404)
|> Phoenix.Controller.put_view(Pleroma.Web.ErrorView)
|> Phoenix.Controller.render("404.json")
|> halt()
end end
end end
def federating?, do: Pleroma.Config.get([:instance, :federating])
defp fail(conn) do
conn
|> put_status(404)
|> Phoenix.Controller.put_view(Pleroma.Web.ErrorView)
|> Phoenix.Controller.render("404.json")
|> halt()
end
end end
...@@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do ...@@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Delivery alias Pleroma.Delivery
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Object.Fetcher alias Pleroma.Object.Fetcher
alias Pleroma.Plugs.EnsureAuthenticatedPlug
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.InternalFetchActor alias Pleroma.Web.ActivityPub.InternalFetchActor
...@@ -18,23 +19,37 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do ...@@ -18,23 +19,37 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Web.ActivityPub.UserView alias Pleroma.Web.ActivityPub.UserView
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.FederatingPlug
alias Pleroma.Web.Federator alias Pleroma.Web.Federator
require Logger require Logger
action_fallback(:errors) action_fallback(:errors)
@federating_only_actions [:internal_fetch, :relay, :relay_following, :relay_followers]
plug(FederatingPlug when action in @federating_only_actions)
plug(
EnsureAuthenticatedPlug,
[unless_func: &FederatingPlug.federating?/0] when action not in @federating_only_actions
)
plug(
EnsureAuthenticatedPlug
when action in [:read_inbox, :update_outbox, :whoami, :upload_media, :following, :followers]
)
plug( plug(
Pleroma.Plugs.Cache, Pleroma.Plugs.Cache,
[query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2] [query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]
when action in [:activity, :object] when action in [:activity, :object]
) )
plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
plug(:set_requester_reachable when action in [:inbox]) plug(:set_requester_reachable when action in [:inbox])
plug(:relay_active? when action in [:relay]) plug(:relay_active? when action in [:relay])
def relay_active?(conn, _) do defp relay_active?(conn, _) do
if Pleroma.Config.get([:instance, :allow_relay]) do if Pleroma.Config.get([:instance, :allow_relay]) do
conn conn
else else
...@@ -127,11 +142,13 @@ defp set_cache_ttl_for(conn, entity) do ...@@ -127,11 +142,13 @@ defp set_cache_ttl_for(conn, entity) do
end end
# GET /relay/following # GET /relay/following
def following(%{assigns: %{relay: true}} = conn, _params) do def relay_following(conn, _params) do
conn with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
|> put_resp_content_type("application/activity+json") conn
|> put_view(UserView) |> put_resp_content_type("application/activity+json")
|> render("following.json", %{user: Relay.get_actor()}) |> put_view(UserView)
|> render("following.json", %{user: Relay.get_actor()})
end
end end
def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
...@@ -164,11 +181,13 @@ def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) d ...@@ -164,11 +181,13 @@ def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) d
end end
# GET /relay/followers # GET /relay/followers
def followers(%{assigns: %{relay: true}} = conn, _params) do def relay_followers(conn, _params) do
conn with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
|> put_resp_content_type("application/activity+json") conn
|> put_view(UserView) |> put_resp_content_type("application/activity+json")
|> render("followers.json", %{user: Relay.get_actor()}) |> put_view(UserView)
|> render("followers.json", %{user: Relay.get_actor()})
end
end end
def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
...@@ -200,13 +219,16 @@ def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) d ...@@ -200,13 +219,16 @@ def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) d
end end
end end
def outbox(conn, %{"nickname" => nickname, "page" => page?} = params) def outbox(
%{assigns: %{user: for_user}} = conn,
%{"nickname" => nickname, "page" => page?} = params
)
when page? in [true, "true"] do when page? in [true, "true"] do
with %User{} = user <- User.get_cached_by_nickname(nickname), with %User{} = user <- User.get_cached_by_nickname(nickname),
{:ok, user} <- User.ensure_keys_present(user) do {:ok, user} <- User.ensure_keys_present(user) do
activities = activities =
if params["max_id"] do if params["max_id"] do
ActivityPub.fetch_user_activities(user, nil, %{ ActivityPub.fetch_user_activities(user, for_user, %{
"max_id" => params["max_id"], "max_id" => params["max_id"],
# This is a hack because postgres generates inefficient queries when filtering by # This is a hack because postgres generates inefficient queries when filtering by
# 'Answer', poll votes will be hidden by the visibility filter in this case anyway # 'Answer', poll votes will be hidden by the visibility filter in this case anyway
...@@ -214,7 +236,7 @@ def outbox(conn, %{"nickname" => nickname, "page" => page?} = params) ...@@ -214,7 +236,7 @@ def outbox(conn, %{"nickname" => nickname, "page" => page?} = params)
"limit" => 10 "limit" => 10
}) })
else else
ActivityPub.fetch_user_activities(user, nil, %{ ActivityPub.fetch_user_activities(user, for_user, %{
"limit" => 10, "limit" => 10,
"include_poll_votes" => true "include_poll_votes" => true
}) })
...@@ -255,8 +277,16 @@ def inbox(%{assigns: %{valid_signature: true}} = conn, params) do ...@@ -255,8 +277,16 @@ def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
json(conn, "ok") json(conn, "ok")
end end
# only accept relayed Creates # POST /relay/inbox -or- POST /internal/fetch/inbox
def inbox(conn, %{"type" => "Create"} = params) do def inbox(conn, params) do
if params["type"] == "Create" && FederatingPlug.federating?() do
post_inbox_relayed_create(conn, params)
else
post_inbox_fallback(conn, params)
end
end
defp post_inbox_relayed_create(conn, params) do
Logger.debug( Logger.debug(
"Signature missing or not from author, relayed Create message, fetching object from source" "Signature missing or not from author, relayed Create message, fetching object from source"
) )
...@@ -266,10 +296,11 @@ def inbox(conn, %{"type" => "Create"} = params) do ...@@ -266,10 +296,11 @@ def inbox(conn, %{"type" => "Create"} = params) do
json(conn, "ok") json(conn, "ok")
end end
def inbox(conn, params) do defp post_inbox_fallback(conn, params) do
headers = Enum.into(conn.req_headers, %{}) headers = Enum.into(conn.req_headers, %{})
if String.contains?(headers["signature"], params["actor"]) do if headers["signature"] && params["actor"] &&
String.contains?(headers["signature"], params["actor"]) do
Logger.debug( Logger.debug(
"Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!" "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
) )
...@@ -277,7 +308,9 @@ def inbox(conn, params) do ...@@ -277,7 +308,9 @@ def inbox(conn, params) do
Logger.debug(inspect(conn.req_headers)) Logger.debug(inspect(conn.req_headers))
end end
json(conn, dgettext("errors", "error")) conn
|> put_status(:bad_request)
|> json(dgettext("errors", "error"))
end end
defp represent_service_actor(%User{} = user, conn) do defp represent_service_actor(%User{} = user, conn) do
...@@ -311,10 +344,8 @@ def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do ...@@ -311,10 +344,8 @@ def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
|> render("user.json", %{user: user}) |> render("user.json", %{user: user})
end end
def whoami(_conn, _params), do: {:error, :not_found}
def read_inbox( def read_inbox(
%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
%{"nickname" => nickname, "page" => page?} = params %{"nickname" => nickname, "page" => page?} = params
) )
when page? in [true, "true"] do when page? in [true, "true"] do
...@@ -337,7 +368,7 @@ def read_inbox( ...@@ -337,7 +368,7 @@ def read_inbox(
}) })
end end
def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{ def read_inbox(%{assigns: %{user: %User{nickname: nickname} = user}} = conn, %{
"nickname" => nickname "nickname" => nickname
}) do }) do
with {:ok, user} <- User.ensure_keys_present(user) do with {:ok, user} <- User.ensure_keys_present(user) do
...@@ -348,15 +379,7 @@ def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{ ...@@ -348,15 +379,7 @@ def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{
end end
end end
def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do def read_inbox(%{assigns: %{user: %User{nickname: as_nickname}}} = conn, %{
err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
conn
|> put_status(:forbidden)
|> json(err)
end
def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
"nickname" => nickname "nickname" => nickname
}) do }) do
err = err =
...@@ -370,7 +393,7 @@ def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{ ...@@ -370,7 +393,7 @@ def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
|> json(err) |> json(err)
end end
def handle_user_activity(user, %{"type" => "Create"} = params) do defp handle_user_activity(%User{} = user, %{"type" => "Create"} = params) do
object = object =
params["object"] params["object"]
|> Map.merge(Map.take(params, ["to", "cc"])) |> Map.merge(Map.take(params, ["to", "cc"]))
...@@ -386,7 +409,7 @@ def handle_user_activity(user, %{"type" => "Create"} = params) do ...@@ -386,7 +409,7 @@ def handle_user_activity(user, %{"type" => "Create"} = params) do
}) })
end end
def handle_user_activity(user, %{"type" => "Delete"} = params) do defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do
with %Object{} = object <- Object.normalize(params["object"]), with %Object{} = object <- Object.normalize(params["object"]),
true <- user.is_moderator || user.ap_id == object.data["actor"], true <- user.is_moderator || user.ap_id == object.data["actor"],
{:ok, delete} <- ActivityPub.delete(object) do {:ok, delete} <- ActivityPub.delete(object) do
...@@ -396,7 +419,7 @@ def handle_user_activity(user, %{"type" => "Delete"} = params) do ...@@ -396,7 +419,7 @@ def handle_user_activity(user, %{"type" => "Delete"} = params) do
end end
end end
def handle_user_activity(user, %{"type" => "Like"} = params) do defp handle_user_activity(%User{} = user, %{"type" => "Like"} = params) do
with %Object{} = object <- Object.normalize(params["object"]), with %Object{} = object <- Object.normalize(params["object"]),
{:ok, activity, _object} <- ActivityPub.like(user, object) do {:ok, activity, _object} <- ActivityPub.like(user, object) do
{:ok, activity} {:ok, activity}
...@@ -405,7 +428,7 @@ def handle_user_activity(user, %{"type" => "Like"} = params) do ...@@ -405,7 +428,7 @@ def handle_user_activity(user, %{"type" => "Like"} = params) do
end end
end end
def handle_user_activity(_, _) do defp handle_user_activity(_, _) do
{:error, dgettext("errors", "Unhandled activity type")} {:error, dgettext("errors", "Unhandled activity type")}
end end
...@@ -434,7 +457,7 @@ def update_outbox( ...@@ -434,7 +457,7 @@ def update_outbox(
end end
end end
def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do def update_outbox(%{assigns: %{user: %User{} = user}} = conn, %{"nickname" => nickname}) do
err = err =
dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}", dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
nickname: nickname, nickname: nickname,
...@@ -446,13 +469,13 @@ def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = ...@@ -446,13 +469,13 @@ def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} =
|> json(err) |> json(err)
end end
def errors(conn, {:error, :not_found}) do defp errors(conn, {:error, :not_found}) do
conn conn
|> put_status(:not_found) |> put_status(:not_found)
|> json(dgettext("errors", "Not found")) |> json(dgettext("errors", "Not found"))
end end
def errors(conn, _e) do defp errors(conn, _e) do
conn conn
|> put_status(:internal_server_error) |> put_status(:internal_server_error)
|> json(dgettext("errors", "error")) |> json(dgettext("errors", "error"))
...@@ -492,7 +515,7 @@ defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do ...@@ -492,7 +515,7 @@ defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
- HTTP Code: 201 Created - HTTP Code: 201 Created
- HTTP Body: ActivityPub object to be inserted into another's `attachment` field - HTTP Body: ActivityPub object to be inserted into another's `attachment` field
""" """
def upload_media(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
with {:ok, object} <- with {:ok, object} <-
ActivityPub.upload( ActivityPub.upload(
file, file,
......
...@@ -25,7 +25,12 @@ def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname ...@@ -25,7 +25,12 @@ def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname
def feed_redirect(%{assigns: %{format: format}} = conn, _params) def feed_redirect(%{assigns: %{format: format}} = conn, _params)
when format in ["json", "activity+json"] do when format in ["json", "activity+json"] do
ActivityPubController.call(conn, :user) with %{halted: false} = conn <-
Pleroma.Plugs.EnsureAuthenticatedPlug.call(conn,
unless_func: &Pleroma.Web.FederatingPlug.federating?/0
) do
ActivityPubController.call(conn, :user)
end
end end
def feed_redirect(conn, %{"nickname" => nickname}) do def feed_redirect(conn, %{"nickname" => nickname}) do
......
...@@ -16,6 +16,10 @@ defmodule Pleroma.Web.OStatus.OStatusController do ...@@ -16,6 +16,10 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Web.Metadata.PlayerView alias Pleroma.Web.Metadata.PlayerView
alias Pleroma.Web.Router alias Pleroma.Web.Router
plug(Pleroma.Plugs.EnsureAuthenticatedPlug,
unless_func: &Pleroma.Web.FederatingPlug.federating?/0
)
plug( plug(
RateLimiter, RateLimiter,
[name: :ap_routes, params: ["uuid"]] when action in [:object, :activity] [name: :ap_routes, params: ["uuid"]] when action in [:object, :activity]
...@@ -135,13 +139,13 @@ def notice_player(conn, %{"id" => id}) do ...@@ -135,13 +139,13 @@ def notice_player(conn, %{"id" => id}) do
end end
end end
def errors(conn, {:error, :not_found}) do defp errors(conn, {:error, :not_found}) do
render_error(conn, :not_found, "Not found") render_error(conn, :not_found, "Not found")
end end
def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found}) defp errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
def errors(conn, _) do defp errors(conn, _) do
render_error(conn, :internal_server_error, "Something went wrong") render_error(conn, :internal_server_error, "Something went wrong")
end end
end end
...@@ -541,6 +541,7 @@ defmodule Pleroma.Web.Router do ...@@ -541,6 +541,7 @@ defmodule Pleroma.Web.Router do
get("/mailer/unsubscribe/:token", Mailer.SubscriptionController, :unsubscribe) get("/mailer/unsubscribe/:token", Mailer.SubscriptionController, :unsubscribe)
end end
# Server to Server (S2S) AP interactions
pipeline :activitypub do pipeline :activitypub do
plug(:accepts, ["activity+json", "json"]) plug(:accepts, ["activity+json", "json"])
plug(Pleroma.Web.Plugs.HTTPSignaturePlug) plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
...@@ -554,6 +555,7 @@ defmodule Pleroma.Web.Router do ...@@ -554,6 +555,7 @@ defmodule Pleroma.Web.Router do
get("/users/:nickname/outbox", ActivityPubController, :outbox) get("/users/:nickname/outbox", ActivityPubController, :outbox)
end end
# Client to Server (C2S) AP interactions
pipeline :activitypub_client do pipeline :activitypub_client do
plug(:accepts, ["activity+json", "json"]) plug(:accepts, ["activity+json", "json"])
plug(:fetch_session) plug(:fetch_session)
...@@ -597,8 +599,8 @@ defmodule Pleroma.Web.Router do ...@@ -597,8 +599,8 @@ defmodule Pleroma.Web.Router do
post("/inbox", ActivityPubController, :inbox) post("/inbox", ActivityPubController, :inbox)
end end
get("/following", ActivityPubController, :following, assigns: %{relay: true}) get("/following", ActivityPubController, :relay_following)
get("/followers", ActivityPubController, :followers, assigns: %{relay: true}) get("/followers", ActivityPubController, :relay_followers)
end end
scope "/internal/fetch", Pleroma.Web.ActivityPub do scope "/internal/fetch", Pleroma.Web.ActivityPub do
......
...@@ -17,6 +17,10 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do ...@@ -17,6 +17,10 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
plug(:put_view, Pleroma.Web.StaticFE.StaticFEView) plug(:put_view, Pleroma.Web.StaticFE.StaticFEView)
plug(:assign_id) plug(:assign_id)
plug(Pleroma.Plugs.EnsureAuthenticatedPlug,
unless_func: &Pleroma.Web.FederatingPlug.federating?/0
)
@page_keys ["max_id", "min_id", "limit", "since_id", "order"] @page_keys ["max_id", "min_id", "limit", "since_id", "order"]
defp get_title(%Object{data: %{"name" => name}}) when is_binary(name), defp get_title(%Object{data: %{"name" => name}}) when is_binary(name),
...@@ -33,7 +37,7 @@ defp not_found(conn, message) do ...@@ -33,7 +37,7 @@ defp not_found(conn, message) do
|> render("error.html", %{message: message, meta: ""}) |> render("error.html", %{message: message, meta: ""})
end end
def get_counts(%Activity{} = activity) do defp get_counts(%Activity{} = activity) do
%Object{data: data} = Object.normalize(activity) %Object{data: data} = Object.normalize(activity)
%{ %{
...@@ -43,9 +47,9 @@ def get_counts(%Activity{} = activity) do ...@@ -43,9 +47,9 @@ def get_counts(%Activity{} = activity) do
} }
end end
def represent(%Activity{} = activity), do: represent(activity, false) defp represent(%Activity{} = activity), do: represent(activity, false)
def represent(%Activity{object: %Object{data: data}} = activity, selected) do defp represent(%Activity{object: %Object{data: data}} = activity, selected) do
{:ok, user} = User.get_or_fetch(activity.object.data["actor"]) {:ok, user} = User.get_or_fetch(activity.object.data["actor"])
link = link =
...@@ -147,17 +151,17 @@ def show(%{assigns: %{activity_id: _}} = conn, _params) do ...@@ -147,17 +151,17 @@ def show(%{assigns: %{activity_id: _}} = conn, _params) do
end end
end end
def assign_id(%{path_info: ["notice", notice_id]} = conn, _opts),