Fix OAuth consumer mode #668

Merged
floatingghost merged 4 commits from :develop into develop 2024-02-02 10:05:42 +00:00
2 changed files with 54 additions and 24 deletions
Showing only changes of commit 77000b8ffd - Show all commits

View file

@ -39,6 +39,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
action_fallback(Pleroma.Web.OAuth.FallbackController) action_fallback(Pleroma.Web.OAuth.FallbackController)
@oob_token_redirect_uri "urn:ietf:wg:oauth:2.0:oob" @oob_token_redirect_uri "urn:ietf:wg:oauth:2.0:oob"
@state_cookie_name "akkoma_oauth_state"
# Note: this definition is only called from error-handling methods with `conn.params` as 2nd arg # Note: this definition is only called from error-handling methods with `conn.params` as 2nd arg
def authorize(%Plug.Conn{} = conn, %{"authorization" => _} = params) do def authorize(%Plug.Conn{} = conn, %{"authorization" => _} = params) do
@ -445,7 +446,7 @@ def prepare_request(%Plug.Conn{} = conn, %{
# Handing the request to Ueberauth # Handing the request to Ueberauth
conn conn
|> put_resp_cookie("akkoma_oauth_state", state) |> put_resp_cookie(@state_cookie_name, state)
|> redirect(to: ~p"/oauth/#{provider}") |> redirect(to: ~p"/oauth/#{provider}")
end end
@ -469,12 +470,18 @@ def callback(%Plug.Conn{assigns: %{ueberauth_failure: failure}} = conn, params)
messages = for e <- Map.get(failure, :errors, []), do: e.message messages = for e <- Map.get(failure, :errors, []), do: e.message
message = Enum.join(messages, "; ") message = Enum.join(messages, "; ")
conn error_message = dgettext("errors", "Failed to authenticate: %{message}.", message: message)
|> put_flash(
:error, if params["redirect_uri"] do
dgettext("errors", "Failed to authenticate: %{message}.", message: message) conn
) |> put_flash(
|> redirect(external: redirect_uri(conn, params["redirect_uri"])) :error,
error_message
)
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
else
send_resp(conn, :bad_request, error_message)
end
end end
def callback(%Plug.Conn{} = conn, params) do def callback(%Plug.Conn{} = conn, params) do
@ -510,7 +517,7 @@ def callback(%Plug.Conn{} = conn, params) do
defp callback_params(%Plug.Conn{} = conn, params) do defp callback_params(%Plug.Conn{} = conn, params) do
fetch_cookies(conn) fetch_cookies(conn)
Map.merge(params, Jason.decode!(Map.get(conn.req_cookies, "akkoma_oauth_state", "{}"))) Map.merge(params, Jason.decode!(Map.get(conn.req_cookies, @state_cookie_name, "{}")))
end end
def registration_details(%Plug.Conn{} = conn, %{"authorization" => auth_attrs}) do def registration_details(%Plug.Conn{} = conn, %{"authorization" => auth_attrs}) do

View file

@ -81,9 +81,7 @@ test "GET /oauth/prepare_request encodes parameters as `state` and redirects", %
assert html_response(conn, 302) assert html_response(conn, 302)
redirect_query = URI.parse(redirected_to(conn)).query assert {:ok, state_components} = Jason.decode(conn.resp_cookies["akkoma_oauth_state"].value)
assert %{"state" => state_param} = URI.decode_query(redirect_query)
assert {:ok, state_components} = Jason.decode(state_param)
expected_client_id = app.client_id expected_client_id = app.client_id
expected_redirect_uri = app.redirect_uris expected_redirect_uri = app.redirect_uris
@ -97,7 +95,7 @@ test "GET /oauth/prepare_request encodes parameters as `state` and redirects", %
end end
test "with user-bound registration, GET /oauth/<provider>/callback redirects to `redirect_uri` with `code`", test "with user-bound registration, GET /oauth/<provider>/callback redirects to `redirect_uri` with `code`",
%{app: app, conn: conn} do %{app: app, conn: _} do
registration = insert(:registration) registration = insert(:registration)
redirect_uri = OAuthController.default_redirect_uri(app) redirect_uri = OAuthController.default_redirect_uri(app)
@ -109,15 +107,17 @@ test "with user-bound registration, GET /oauth/<provider>/callback redirects to
} }
conn = conn =
conn build_conn()
|> put_req_cookie("akkoma_oauth_state", Jason.encode!(state_params))
|> Plug.Session.call(Plug.Session.init(@session_opts))
|> fetch_session()
|> assign(:ueberauth_auth, %{provider: registration.provider, uid: registration.uid}) |> assign(:ueberauth_auth, %{provider: registration.provider, uid: registration.uid})
|> get( |> get(
"/oauth/twitter/callback", "/oauth/twitter/callback",
%{ %{
"oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
"oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
"provider" => "twitter", "provider" => "twitter"
"state" => Jason.encode!(state_params)
} }
) )
@ -162,15 +162,42 @@ test "with user-unbound registration, GET /oauth/<provider>/callback renders reg
test "on authentication error, GET /oauth/<provider>/callback redirects to `redirect_uri`", %{ test "on authentication error, GET /oauth/<provider>/callback redirects to `redirect_uri`", %{
app: app, app: app,
conn: conn conn: _
} do } do
state_params = %{ state_params = %{
"scope" => Enum.join(app.scopes, " "), "scope" => Enum.join(app.scopes, " "),
"client_id" => app.client_id, "client_id" => app.client_id,
"redirect_uri" => OAuthController.default_redirect_uri(app), "redirect_uri" => OAuthController.default_redirect_uri(app)
"state" => ""
} }
conn =
build_conn()
|> put_req_cookie("akkoma_oauth_state", Jason.encode!(state_params))
|> Plug.Session.call(Plug.Session.init(@session_opts))
|> fetch_session()
|> assign(:ueberauth_failure, %{errors: [%{message: "(error description)"}]})
|> get(
"/oauth/twitter/callback",
%{
"oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
"oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
"provider" => "twitter",
"state" => ""
}
)
assert html_response(conn, 302)
assert redirected_to(conn) == app.redirect_uris
assert Phoenix.Flash.get(conn.assigns.flash, :error) ==
"Failed to authenticate: (error description)."
end
test "on authentication error with no prior state, GET /oauth/<provider>/callback returns 400",
%{
app: _,
conn: conn
} do
conn = conn =
conn conn
|> assign(:ueberauth_failure, %{errors: [%{message: "(error description)"}]}) |> assign(:ueberauth_failure, %{errors: [%{message: "(error description)"}]})
@ -180,15 +207,11 @@ test "on authentication error, GET /oauth/<provider>/callback redirects to `redi
"oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
"oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
"provider" => "twitter", "provider" => "twitter",
"state" => Jason.encode!(state_params) "state" => ""
} }
) )
assert html_response(conn, 302) assert response(conn, 400)
assert redirected_to(conn) == app.redirect_uris
assert Phoenix.Flash.get(conn.assigns.flash, :error) ==
"Failed to authenticate: (error description)."
end end
test "GET /oauth/registration_details renders registration details form", %{ test "GET /oauth/registration_details renders registration details form", %{