Allow unsigned fetches of a user's public key
This commit is contained in:
parent
4f9f16587b
commit
4d3f52dcc6
5 changed files with 94 additions and 10 deletions
|
@ -23,8 +23,12 @@ defmodule Pleroma.User.SigningKey do
|
|||
|> Repo.preload(:signing_key)
|
||||
end
|
||||
|
||||
def key_id_of_local_user(%User{local: true, signing_key: %__MODULE__{key_id: key_id}}),
|
||||
do: key_id
|
||||
def key_id_of_local_user(%User{local: true} = user) do
|
||||
case Repo.preload(user, :signing_key) do
|
||||
%User{signing_key: %__MODULE__{key_id: key_id}} -> key_id
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@spec remote_changeset(__MODULE__, map) :: Changeset.t()
|
||||
def remote_changeset(%__MODULE__{} = signing_key, attrs) do
|
||||
|
@ -119,9 +123,16 @@ defmodule Pleroma.User.SigningKey do
|
|||
|
||||
def public_key(_), do: {:error, "key not found"}
|
||||
|
||||
def public_key_pem(%User{signing_key: %__MODULE__{public_key: public_key_pem}}),
|
||||
do: public_key_pem
|
||||
def public_key_pem(%User{} = user) do
|
||||
case Repo.preload(user, :signing_key) do
|
||||
%User{signing_key: %__MODULE__{public_key: public_key_pem}} -> {:ok, public_key_pem}
|
||||
_ -> {:error, "key not found"}
|
||||
end
|
||||
end
|
||||
|
||||
def public_key_pem(e) do
|
||||
{:error, "key not found"}
|
||||
end
|
||||
@spec private_key(User.t()) :: {:ok, binary()} | {:error, String.t()}
|
||||
@doc """
|
||||
Given a user, return the private key for that user in binary format.
|
||||
|
@ -146,7 +157,8 @@ defmodule Pleroma.User.SigningKey do
|
|||
So if we're rejected, we should probably just give up.
|
||||
"""
|
||||
def fetch_remote_key(key_id) do
|
||||
resp = HTTP.Backoff.get(key_id)
|
||||
# we should probably sign this, just in case
|
||||
resp = Pleroma.Object.Fetcher.get_object(key_id)
|
||||
|
||||
case handle_signature_response(resp) do
|
||||
{:ok, ap_id, public_key_pem} ->
|
||||
|
|
|
@ -60,7 +60,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
end
|
||||
end
|
||||
|
||||
def user(conn, %{"nickname" => nickname}) do
|
||||
|
||||
@doc """
|
||||
Render the user's AP data
|
||||
WARNING: we cannot actually check if the request has a fragment! so let's play defensively
|
||||
- IF we have a valid signature, serve full user
|
||||
- IF we do not, and authorized_fetch_mode is enabled, serve the key only
|
||||
- OTHERWISE, serve the full actor (since we don't need to worry about the signature)
|
||||
"""
|
||||
def user(%{assigns: %{valid_signature: true}} = conn, params) do
|
||||
render_full_user(conn, params)
|
||||
end
|
||||
|
||||
def user(conn, params) do
|
||||
if Pleroma.Config.get([:activitypub, :authorized_fetch_mode], false) do
|
||||
render_key_only_user(conn, params)
|
||||
else
|
||||
render_full_user(conn, params)
|
||||
end
|
||||
end
|
||||
|
||||
defp render_full_user(conn, %{"nickname" => nickname}) do
|
||||
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||
conn
|
||||
|> put_resp_content_type("application/activity+json")
|
||||
|
@ -72,6 +92,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
end
|
||||
end
|
||||
|
||||
def render_key_only_user(conn, %{"nickname" => nickname}) do
|
||||
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||
conn
|
||||
|> put_resp_content_type("application/activity+json")
|
||||
|> put_view(UserView)
|
||||
|> render("keys.json", %{user: user})
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
%{local: false} -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
def object(%{assigns: assigns} = conn, _) do
|
||||
with ap_id <- Endpoint.url() <> conn.request_path,
|
||||
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
|
||||
|
|
|
@ -32,7 +32,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
def render("endpoints.json", _), do: %{}
|
||||
|
||||
def render("service.json", %{user: user}) do
|
||||
public_key = User.SigningKey.public_key_pem(user)
|
||||
{:ok, public_key} = User.SigningKey.public_key_pem(user)
|
||||
|
||||
endpoints = render("endpoints.json", %{user: user})
|
||||
|
||||
|
@ -67,7 +67,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname)
|
||||
|
||||
def render("user.json", %{user: user}) do
|
||||
public_key = User.SigningKey.public_key_pem(user)
|
||||
{:ok, public_key} = User.SigningKey.public_key_pem(user)
|
||||
user = User.sanitize_html(user)
|
||||
|
||||
endpoints = render("endpoints.json", %{user: user})
|
||||
|
@ -112,7 +112,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
end
|
||||
|
||||
def render("keys.json", %{user: user}) do
|
||||
public_key = User.SigningKey.public_key_pem(user)
|
||||
{:ok, public_key} = User.SigningKey.public_key_pem(user)
|
||||
|
||||
%{
|
||||
"id" => user.ap_id,
|
||||
|
|
33
lib/pleroma/web/plugs/ensure_user_public_key_plug.ex
Normal file
33
lib/pleroma/web/plugs/ensure_user_public_key_plug.ex
Normal file
|
@ -0,0 +1,33 @@
|
|||
defmodule Pleroma.Web.Plugs.EnsureUserPublicKeyPlug do
|
||||
@moduledoc """
|
||||
This plug will attempt to pull in a user's public key if we do not have it.
|
||||
We _should_ be able to request the URL from the key URL...
|
||||
"""
|
||||
|
||||
import Plug.Conn
|
||||
|
||||
alias Pleroma.User
|
||||
|
||||
def init(options), do: options
|
||||
|
||||
def call(conn, _opts) do
|
||||
key_id = key_id_from_conn(conn)
|
||||
unless is_nil(key_id) do
|
||||
SigningKey.fetch_remote_key(key_id)
|
||||
# now we SHOULD have the user that owns the key locally. maybe.
|
||||
# if we don't, we'll error out when we try to validate.
|
||||
end
|
||||
|
||||
conn
|
||||
end
|
||||
|
||||
defp key_id_from_conn(conn) do
|
||||
case HTTPSignatures.signature_for_conn(conn) do
|
||||
%{"keyId" => key_id} when is_binary(key_id) ->
|
||||
key_id
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
|
@ -144,7 +144,14 @@ defmodule Pleroma.Web.Router do
|
|||
})
|
||||
end
|
||||
|
||||
pipeline :optional_http_signature do
|
||||
plug(Pleroma.Web.Plugs.EnsureUserPublicKeyPlug)
|
||||
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
|
||||
plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
|
||||
end
|
||||
|
||||
pipeline :http_signature do
|
||||
plug(Pleroma.Web.Plugs.EnsureUserPublicKeyPlug)
|
||||
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
|
||||
plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
|
||||
plug(Pleroma.Web.Plugs.EnsureHTTPSignaturePlug)
|
||||
|
@ -745,7 +752,7 @@ defmodule Pleroma.Web.Router do
|
|||
scope "/", Pleroma.Web do
|
||||
# Note: html format is supported only if static FE is enabled
|
||||
# Note: http signature is only considered for json requests (no auth for non-json requests)
|
||||
pipe_through([:accepts_html_xml_json, :http_signature, :static_fe])
|
||||
pipe_through([:accepts_html_xml_json, :optional_http_signature, :static_fe])
|
||||
|
||||
# Note: returns user _profile_ for json requests, redirects to user _feed_ for non-json ones
|
||||
get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed)
|
||||
|
|
Loading…
Reference in a new issue