X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fweb%2Foauth%2Foauth_controller.ex;h=fe71aca8cea95f26f5523a439674ce620aa30cbd;hb=1bd1f62af55e01613e6362661b36a19091c87424;hp=79d803295fdbda618c1ffd573978e3adaa55884e;hpb=eda4b00a6a76dc282fe83582e7e24209379d4025;p=akkoma diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 79d803295..fe71aca8c 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do use Pleroma.Web, :controller + alias Pleroma.Helpers.UriHelper alias Pleroma.Registration alias Pleroma.Repo alias Pleroma.User @@ -23,37 +24,29 @@ defmodule Pleroma.Web.OAuth.OAuthController do plug(:fetch_session) plug(:fetch_flash) + plug(Pleroma.Plugs.RateLimiter, :authentication when action == :create_authorization) action_fallback(Pleroma.Web.OAuth.FallbackController) + @oob_token_redirect_uri "urn:ietf:wg:oauth:2.0:oob" + # Note: this definition is only called from error-handling methods with `conn.params` as 2nd arg - def authorize(conn, %{"authorization" => _} = params) do + def authorize(%Plug.Conn{} = conn, %{"authorization" => _} = params) do {auth_attrs, params} = Map.pop(params, "authorization") authorize(conn, Map.merge(params, auth_attrs)) end - def authorize(%{assigns: %{token: %Token{} = token}} = conn, params) do + def authorize(%Plug.Conn{assigns: %{token: %Token{}}} = conn, params) do if ControllerHelper.truthy_param?(params["force_login"]) do do_authorize(conn, params) else - redirect_uri = - if is_binary(params["redirect_uri"]) do - params["redirect_uri"] - else - app = Repo.preload(token, :app).app - - app.redirect_uris - |> String.split() - |> Enum.at(0) - end - - redirect(conn, external: redirect_uri(conn, redirect_uri)) + handle_existing_authorization(conn, params) end end - def authorize(conn, params), do: do_authorize(conn, params) + def authorize(%Plug.Conn{} = conn, params), do: do_authorize(conn, params) - defp do_authorize(conn, params) do + defp do_authorize(%Plug.Conn{} = conn, params) do app = Repo.get_by(App, client_id: params["client_id"]) available_scopes = (app && app.scopes) || [] scopes = Scopes.fetch_scopes(params, available_scopes) @@ -70,8 +63,41 @@ defmodule Pleroma.Web.OAuth.OAuthController do }) end + defp handle_existing_authorization( + %Plug.Conn{assigns: %{token: %Token{} = token}} = conn, + %{"redirect_uri" => @oob_token_redirect_uri} + ) do + render(conn, "oob_token_exists.html", %{token: token}) + end + + defp handle_existing_authorization( + %Plug.Conn{assigns: %{token: %Token{} = token}} = conn, + %{} = params + ) do + app = Repo.preload(token, :app).app + + redirect_uri = + if is_binary(params["redirect_uri"]) do + params["redirect_uri"] + else + default_redirect_uri(app) + end + + if redirect_uri in String.split(app.redirect_uris) do + redirect_uri = redirect_uri(conn, redirect_uri) + url_params = %{access_token: token.token} + url_params = UriHelper.append_param_if_present(url_params, :state, params["state"]) + url = UriHelper.append_uri_params(redirect_uri, url_params) + redirect(conn, external: url) + else + conn + |> put_flash(:error, dgettext("errors", "Unlisted redirect_uri.")) + |> redirect(external: redirect_uri(conn, redirect_uri)) + end + end + def create_authorization( - conn, + %Plug.Conn{} = conn, %{"authorization" => _} = params, opts \\ [] ) do @@ -83,35 +109,33 @@ defmodule Pleroma.Web.OAuth.OAuthController do end end - def after_create_authorization(conn, auth, %{ - "authorization" => %{"redirect_uri" => redirect_uri} = auth_attrs + def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{ + "authorization" => %{"redirect_uri" => @oob_token_redirect_uri} }) do - redirect_uri = redirect_uri(conn, redirect_uri) - - if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" do - render(conn, "results.html", %{ - auth: auth - }) - else - connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?" - url = "#{redirect_uri}#{connector}" - url_params = %{:code => auth.token} - - url_params = - if auth_attrs["state"] do - Map.put(url_params, :state, auth_attrs["state"]) - else - url_params - end - - url = "#{url}#{Plug.Conn.Query.encode(url_params)}" + render(conn, "oob_authorization_created.html", %{auth: auth}) + end + def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{ + "authorization" => %{"redirect_uri" => redirect_uri} = auth_attrs + }) do + app = Repo.preload(auth, :app).app + + # An extra safety measure before we redirect (also done in `do_create_authorization/2`) + if redirect_uri in String.split(app.redirect_uris) do + redirect_uri = redirect_uri(conn, redirect_uri) + url_params = %{code: auth.token} + url_params = UriHelper.append_param_if_present(url_params, :state, auth_attrs["state"]) + url = UriHelper.append_uri_params(redirect_uri, url_params) redirect(conn, external: url) + else + conn + |> put_flash(:error, dgettext("errors", "Unlisted redirect_uri.")) + |> redirect(external: redirect_uri(conn, redirect_uri)) end end defp handle_create_authorization_error( - conn, + %Plug.Conn{} = conn, {:error, scopes_issue}, %{"authorization" => _} = params ) @@ -119,31 +143,31 @@ defmodule Pleroma.Web.OAuth.OAuthController do # Per https://github.com/tootsuite/mastodon/blob/ # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39 conn - |> put_flash(:error, "This action is outside the authorized scopes") + |> put_flash(:error, dgettext("errors", "This action is outside the authorized scopes")) |> put_status(:unauthorized) |> authorize(params) end defp handle_create_authorization_error( - conn, + %Plug.Conn{} = conn, {:auth_active, false}, %{"authorization" => _} = params ) do # Per https://github.com/tootsuite/mastodon/blob/ # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76 conn - |> put_flash(:error, "Your login is missing a confirmed e-mail address") + |> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address")) |> put_status(:forbidden) |> authorize(params) end - defp handle_create_authorization_error(conn, error, %{"authorization" => _}) do + defp handle_create_authorization_error(%Plug.Conn{} = conn, error, %{"authorization" => _}) do Authenticator.handle_error(conn, error) end @doc "Renew access_token with refresh_token" def token_exchange( - conn, + %Plug.Conn{} = conn, %{"grant_type" => "refresh_token", "refresh_token" => token} = _params ) do with {:ok, app} <- Token.Utils.fetch_app(conn), @@ -153,13 +177,11 @@ defmodule Pleroma.Web.OAuth.OAuthController do json(conn, Token.Response.build(user, token, response_attrs)) else - _error -> - put_status(conn, 400) - |> json(%{error: "Invalid credentials"}) + _error -> render_invalid_credentials_error(conn) end end - def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do + def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "authorization_code"} = params) do with {:ok, app} <- Token.Utils.fetch_app(conn), fixed_token = Token.Utils.fix_padding(params["code"]), {:ok, auth} <- Authorization.get_by_token(app, fixed_token), @@ -169,20 +191,20 @@ defmodule Pleroma.Web.OAuth.OAuthController do json(conn, Token.Response.build(user, token, response_attrs)) else - _error -> - put_status(conn, 400) - |> json(%{error: "Invalid credentials"}) + _error -> render_invalid_credentials_error(conn) end end def token_exchange( - conn, + %Plug.Conn{} = conn, %{"grant_type" => "password"} = params ) do with {:ok, %User{} = user} <- Authenticator.get_user(conn), {:ok, app} <- Token.Utils.fetch_app(conn), {:auth_active, true} <- {:auth_active, User.auth_active?(user)}, - {:user_active, true} <- {:user_active, !user.info.deactivated}, + {:user_active, true} <- {:user_active, !user.deactivated}, + {:password_reset_pending, false} <- + {:password_reset_pending, user.password_reset_pending}, {:ok, scopes} <- validate_scopes(app, params), {:ok, auth} <- Authorization.create_authorization(app, user, scopes), {:ok, token} <- Token.exchange_token(app, auth) do @@ -191,23 +213,39 @@ defmodule Pleroma.Web.OAuth.OAuthController do {:auth_active, false} -> # Per https://github.com/tootsuite/mastodon/blob/ # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76 - conn - |> put_status(:forbidden) - |> json(%{error: "Your login is missing a confirmed e-mail address"}) + render_error( + conn, + :forbidden, + "Your login is missing a confirmed e-mail address", + %{}, + "missing_confirmed_email" + ) {:user_active, false} -> - conn - |> put_status(:forbidden) - |> json(%{error: "Your account is currently disabled"}) + render_error( + conn, + :forbidden, + "Your account is currently disabled", + %{}, + "account_is_disabled" + ) + + {:password_reset_pending, true} -> + render_error( + conn, + :forbidden, + "Password reset is required", + %{}, + "password_reset_required" + ) _error -> - put_status(conn, 400) - |> json(%{error: "Invalid credentials"}) + render_invalid_credentials_error(conn) end end def token_exchange( - conn, + %Plug.Conn{} = conn, %{"grant_type" => "password", "name" => name, "password" => _password} = params ) do params = @@ -218,22 +256,20 @@ defmodule Pleroma.Web.OAuth.OAuthController do token_exchange(conn, params) end - def token_exchange(conn, %{"grant_type" => "client_credentials"} = _params) do + def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "client_credentials"} = _params) do with {:ok, app} <- Token.Utils.fetch_app(conn), {:ok, auth} <- Authorization.create_authorization(app, %User{}), {:ok, token} <- Token.exchange_token(app, auth) do json(conn, Token.Response.build_for_client_credentials(token)) else - _error -> - put_status(conn, 400) - |> json(%{error: "Invalid credentials"}) + _error -> render_invalid_credentials_error(conn) end end # Bad request - def token_exchange(conn, params), do: bad_request(conn, params) + def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params) - def token_revoke(conn, %{"token" => _token} = params) do + def token_revoke(%Plug.Conn{} = conn, %{"token" => _token} = params) do with {:ok, app} <- Token.Utils.fetch_app(conn), {:ok, _token} <- RevokeToken.revoke(app, params) do json(conn, %{}) @@ -244,17 +280,18 @@ defmodule Pleroma.Web.OAuth.OAuthController do end end - def token_revoke(conn, params), do: bad_request(conn, params) + def token_revoke(%Plug.Conn{} = conn, params), do: bad_request(conn, params) # Response for bad request - defp bad_request(conn, _) do - conn - |> put_status(500) - |> json(%{error: "Bad request"}) + defp bad_request(%Plug.Conn{} = conn, _) do + render_error(conn, :internal_server_error, "Bad request") end @doc "Prepares OAuth request to provider for Ueberauth" - def prepare_request(conn, %{"provider" => provider, "authorization" => auth_attrs}) do + def prepare_request(%Plug.Conn{} = conn, %{ + "provider" => provider, + "authorization" => auth_attrs + }) do scope = auth_attrs |> Scopes.fetch_scopes([]) @@ -275,12 +312,14 @@ defmodule Pleroma.Web.OAuth.OAuthController do redirect(conn, to: o_auth_path(conn, :request, provider, params)) end - def request(conn, params) do + def request(%Plug.Conn{} = conn, params) do message = if params["provider"] do - "Unsupported OAuth provider: #{params["provider"]}." + dgettext("errors", "Unsupported OAuth provider: %{provider}.", + provider: params["provider"] + ) else - "Bad OAuth request." + dgettext("errors", "Bad OAuth request.") end conn @@ -288,17 +327,20 @@ defmodule Pleroma.Web.OAuth.OAuthController do |> redirect(to: "/") end - def callback(%{assigns: %{ueberauth_failure: failure}} = conn, params) do + def callback(%Plug.Conn{assigns: %{ueberauth_failure: failure}} = conn, params) do params = callback_params(params) messages = for e <- Map.get(failure, :errors, []), do: e.message message = Enum.join(messages, "; ") conn - |> put_flash(:error, "Failed to authenticate: #{message}.") + |> put_flash( + :error, + dgettext("errors", "Failed to authenticate: %{message}.", message: message) + ) |> redirect(external: redirect_uri(conn, params["redirect_uri"])) end - def callback(conn, params) do + def callback(%Plug.Conn{} = conn, params) do params = callback_params(params) with {:ok, registration} <- Authenticator.get_registration(conn) do @@ -316,7 +358,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do }) conn - |> put_session(:registration_id, registration.id) + |> put_session_registration_id(registration.id) |> registration_details(%{"authorization" => registration_params}) end else @@ -324,7 +366,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do Logger.debug(inspect(["OAUTH_ERROR", error, conn.assigns])) conn - |> put_flash(:error, "Failed to set up user account.") + |> put_flash(:error, dgettext("errors", "Failed to set up user account.")) |> redirect(external: redirect_uri(conn, params["redirect_uri"])) end end @@ -333,7 +375,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do Map.merge(params, Jason.decode!(state)) end - def registration_details(conn, %{"authorization" => auth_attrs}) do + def registration_details(%Plug.Conn{} = conn, %{"authorization" => auth_attrs}) do render(conn, "register.html", %{ client_id: auth_attrs["client_id"], redirect_uri: auth_attrs["redirect_uri"], @@ -344,11 +386,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do }) end - def register(conn, %{"authorization" => _, "op" => "connect"} = params) do + def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "connect"} = params) do with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn), %Registration{} = registration <- Repo.get(Registration, registration_id), - {_, {:ok, auth}} <- - {:create_authorization, do_create_authorization(conn, params)}, + {_, {:ok, auth}} <- {:create_authorization, do_create_authorization(conn, params)}, %User{} = user <- Repo.preload(auth, :user).user, {:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do conn @@ -363,7 +404,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do end end - def register(conn, %{"authorization" => _, "op" => "register"} = params) do + def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "register"} = params) do with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn), %Registration{} = registration <- Repo.get(Registration, registration_id), {:ok, user} <- Authenticator.create_from_registration(conn, registration) do @@ -399,7 +440,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do end defp do_create_authorization( - conn, + %Plug.Conn{} = conn, %{ "authorization" => %{ @@ -420,13 +461,13 @@ defmodule Pleroma.Web.OAuth.OAuthController do end # Special case: Local MastodonFE - defp redirect_uri(conn, "."), do: mastodon_api_url(conn, :login) + defp redirect_uri(%Plug.Conn{} = conn, "."), do: auth_url(conn, :login) - defp redirect_uri(_conn, redirect_uri), do: redirect_uri + defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri - defp get_session_registration_id(conn), do: get_session(conn, :registration_id) + defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :registration_id) - defp put_session_registration_id(conn, registration_id), + defp put_session_registration_id(%Plug.Conn{} = conn, registration_id), do: put_session(conn, :registration_id, registration_id) @spec validate_scopes(App.t(), map()) :: @@ -434,6 +475,16 @@ defmodule Pleroma.Web.OAuth.OAuthController do defp validate_scopes(app, params) do params |> Scopes.fetch_scopes(app.scopes) - |> Scopes.validates(app.scopes) + |> Scopes.validate(app.scopes) + end + + def default_redirect_uri(%App{} = app) do + app.redirect_uris + |> String.split() + |> Enum.at(0) + end + + defp render_invalid_credentials_error(conn) do + render_error(conn, :bad_request, "Invalid credentials") end end