From af690d10336124968e2a0fe0e73decb2d48819cb Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 1 Oct 2019 15:54:45 +0700 Subject: [PATCH] Extract auth actions from `MastodonAPIController` to `AuthController` --- .../controllers/auth_controller.ex | 91 +++++++++++++ .../controllers/mastodon_api_controller.ex | 79 ------------ lib/pleroma/web/router.ex | 6 +- .../controllers/auth_controller_test.exs | 121 ++++++++++++++++++ .../mastodon_api_controller_test.exs | 91 ------------- 5 files changed, 215 insertions(+), 173 deletions(-) create mode 100644 lib/pleroma/web/mastodon_api/controllers/auth_controller.ex create mode 100644 test/web/mastodon_api/controllers/auth_controller_test.exs diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex new file mode 100644 index 000000000..0dee670af --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex @@ -0,0 +1,91 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.AuthController do + use Pleroma.Web, :controller + + alias Pleroma.User + alias Pleroma.Web.OAuth.App + alias Pleroma.Web.OAuth.Authorization + alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.TwitterAPI.TwitterAPI + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + + @local_mastodon_name "Mastodon-Local" + + plug(Pleroma.Plugs.RateLimiter, :password_reset when action == :password_reset) + + @doc "GET /web/login" + def login(%{assigns: %{user: %User{}}} = conn, _params) do + redirect(conn, to: local_mastodon_root_path(conn)) + end + + @doc "Local Mastodon FE login init action" + def login(conn, %{"code" => auth_token}) do + with {:ok, app} <- get_or_make_app(), + {:ok, auth} <- Authorization.get_by_token(app, auth_token), + {:ok, token} <- Token.exchange_token(app, auth) do + conn + |> put_session(:oauth_token, token.token) + |> redirect(to: local_mastodon_root_path(conn)) + end + end + + @doc "Local Mastodon FE callback action" + def login(conn, _) do + with {:ok, app} <- get_or_make_app() do + path = + o_auth_path(conn, :authorize, + response_type: "code", + client_id: app.client_id, + redirect_uri: ".", + scope: Enum.join(app.scopes, " ") + ) + + redirect(conn, to: path) + end + end + + @doc "DELETE /auth/sign_out" + def logout(conn, _) do + conn + |> clear_session + |> redirect(to: "/") + end + + @doc "POST /auth/password" + def password_reset(conn, params) do + nickname_or_email = params["email"] || params["nickname"] + + with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do + conn + |> put_status(:no_content) + |> json("") + else + {:error, "unknown user"} -> + send_resp(conn, :not_found, "") + + {:error, _} -> + send_resp(conn, :bad_request, "") + end + end + + defp local_mastodon_root_path(conn) do + case get_session(conn, :return_to) do + nil -> + mastodon_api_path(conn, :index, ["getting-started"]) + + return_to -> + delete_session(conn, :return_to) + return_to + end + end + + @spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} + defp get_or_make_app do + %{client_name: @local_mastodon_name, redirect_uris: "."} + |> App.get_or_make(["read", "write", "follow", "push"]) + end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 80a7b5bef..4fa0e1bcc 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -10,7 +10,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Bookmark alias Pleroma.Config alias Pleroma.Pagination - alias Pleroma.Plugs.RateLimiter alias Pleroma.Stats alias Pleroma.User alias Pleroma.Web @@ -19,18 +18,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.MastodonView alias Pleroma.Web.MastodonAPI.StatusView - alias Pleroma.Web.OAuth.App - alias Pleroma.Web.OAuth.Authorization - alias Pleroma.Web.OAuth.Token - alias Pleroma.Web.TwitterAPI.TwitterAPI require Logger - plug(RateLimiter, :password_reset when action == :password_reset) - action_fallback(Pleroma.Web.MastodonAPI.FallbackController) - @local_mastodon_name "Mastodon-Local" @mastodon_api_level "2.7.2" def masto_instance(conn, _params) do @@ -264,61 +256,6 @@ def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _para end end - def login(%{assigns: %{user: %User{}}} = conn, _params) do - redirect(conn, to: local_mastodon_root_path(conn)) - end - - @doc "Local Mastodon FE login init action" - def login(conn, %{"code" => auth_token}) do - with {:ok, app} <- get_or_make_app(), - {:ok, auth} <- Authorization.get_by_token(app, auth_token), - {:ok, token} <- Token.exchange_token(app, auth) do - conn - |> put_session(:oauth_token, token.token) - |> redirect(to: local_mastodon_root_path(conn)) - end - end - - @doc "Local Mastodon FE callback action" - def login(conn, _) do - with {:ok, app} <- get_or_make_app() do - path = - o_auth_path(conn, :authorize, - response_type: "code", - client_id: app.client_id, - redirect_uri: ".", - scope: Enum.join(app.scopes, " ") - ) - - redirect(conn, to: path) - end - end - - defp local_mastodon_root_path(conn) do - case get_session(conn, :return_to) do - nil -> - mastodon_api_path(conn, :index, ["getting-started"]) - - return_to -> - delete_session(conn, :return_to) - return_to - end - end - - @spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} - defp get_or_make_app do - App.get_or_make( - %{client_name: @local_mastodon_name, redirect_uris: "."}, - ["read", "write", "follow", "push"] - ) - end - - def logout(conn, _) do - conn - |> clear_session - |> redirect(to: "/") - end - # Stubs for unimplemented mastodon api # def empty_array(conn, _) do @@ -331,22 +268,6 @@ def empty_object(conn, _) do json(conn, %{}) end - def password_reset(conn, params) do - nickname_or_email = params["email"] || params["nickname"] - - with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do - conn - |> put_status(:no_content) - |> json("") - else - {:error, "unknown user"} -> - send_resp(conn, :not_found, "") - - {:error, _} -> - send_resp(conn, :bad_request, "") - end - end - defp present?(nil), do: false defp present?(false), do: false defp present?(_), do: true diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 29f53108c..501978994 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -661,10 +661,10 @@ defmodule Pleroma.Web.Router do scope "/", Pleroma.Web.MastodonAPI do pipe_through(:mastodon_html) - get("/web/login", MastodonAPIController, :login) - delete("/auth/sign_out", MastodonAPIController, :logout) + get("/web/login", AuthController, :login) + delete("/auth/sign_out", AuthController, :logout) - post("/auth/password", MastodonAPIController, :password_reset) + post("/auth/password", AuthController, :password_reset) scope [] do pipe_through(:oauth_read) diff --git a/test/web/mastodon_api/controllers/auth_controller_test.exs b/test/web/mastodon_api/controllers/auth_controller_test.exs new file mode 100644 index 000000000..98b2a82e7 --- /dev/null +++ b/test/web/mastodon_api/controllers/auth_controller_test.exs @@ -0,0 +1,121 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.AuthControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Config + alias Pleroma.Repo + alias Pleroma.Tests.ObanHelpers + + import Pleroma.Factory + import Swoosh.TestAssertions + + describe "GET /web/login" do + setup %{conn: conn} do + session_opts = [ + store: :cookie, + key: "_test", + signing_salt: "cooldude" + ] + + conn = + conn + |> Plug.Session.call(Plug.Session.init(session_opts)) + |> fetch_session() + + test_path = "/web/statuses/test" + %{conn: conn, path: test_path} + end + + test "redirects to the saved path after log in", %{conn: conn, path: path} do + app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") + auth = insert(:oauth_authorization, app: app) + + conn = + conn + |> put_session(:return_to, path) + |> get("/web/login", %{code: auth.token}) + + assert conn.status == 302 + assert redirected_to(conn) == path + end + + test "redirects to the getting-started page when referer is not present", %{conn: conn} do + app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") + auth = insert(:oauth_authorization, app: app) + + conn = get(conn, "/web/login", %{code: auth.token}) + + assert conn.status == 302 + assert redirected_to(conn) == "/web/getting-started" + end + end + + describe "POST /auth/password, with valid parameters" do + setup %{conn: conn} do + user = insert(:user) + conn = post(conn, "/auth/password?email=#{user.email}") + %{conn: conn, user: user} + end + + test "it returns 204", %{conn: conn} do + assert json_response(conn, :no_content) + end + + test "it creates a PasswordResetToken record for user", %{user: user} do + token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) + assert token_record + end + + test "it sends an email to user", %{user: user} do + ObanHelpers.perform_all() + token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) + + email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token) + notify_email = Config.get([:instance, :notify_email]) + instance_name = Config.get([:instance, :name]) + + assert_email_sent( + from: {instance_name, notify_email}, + to: {user.name, user.email}, + html_body: email.html_body + ) + end + end + + describe "POST /auth/password, with invalid parameters" do + setup do + user = insert(:user) + {:ok, user: user} + end + + test "it returns 404 when user is not found", %{conn: conn, user: user} do + conn = post(conn, "/auth/password?email=nonexisting_#{user.email}") + assert conn.status == 404 + assert conn.resp_body == "" + end + + test "it returns 400 when user is not local", %{conn: conn, user: user} do + {:ok, user} = Repo.update(Ecto.Changeset.change(user, local: false)) + conn = post(conn, "/auth/password?email=#{user.email}") + assert conn.status == 400 + assert conn.resp_body == "" + end + end + + describe "DELETE /auth/sign_out" do + test "redirect to root page", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> delete("/auth/sign_out") + + assert conn.status == 302 + assert redirected_to(conn) == "/" + end + end +end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 68fe751e7..2ec5ad2be 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -9,12 +9,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do alias Pleroma.Config alias Pleroma.Notification alias Pleroma.Repo - alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.Web.CommonAPI import Pleroma.Factory - import Swoosh.TestAssertions import Tesla.Mock setup do @@ -303,95 +301,6 @@ test "saves referer path to session", %{conn: conn, path: path} do assert return_to == path end - - test "redirects to the saved path after log in", %{conn: conn, path: path} do - app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") - auth = insert(:oauth_authorization, app: app) - - conn = - conn - |> put_session(:return_to, path) - |> get("/web/login", %{code: auth.token}) - - assert conn.status == 302 - assert redirected_to(conn) == path - end - - test "redirects to the getting-started page when referer is not present", %{conn: conn} do - app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") - auth = insert(:oauth_authorization, app: app) - - conn = get(conn, "/web/login", %{code: auth.token}) - - assert conn.status == 302 - assert redirected_to(conn) == "/web/getting-started" - end - end - - describe "POST /auth/password, with valid parameters" do - setup %{conn: conn} do - user = insert(:user) - conn = post(conn, "/auth/password?email=#{user.email}") - %{conn: conn, user: user} - end - - test "it returns 204", %{conn: conn} do - assert json_response(conn, :no_content) - end - - test "it creates a PasswordResetToken record for user", %{user: user} do - token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) - assert token_record - end - - test "it sends an email to user", %{user: user} do - ObanHelpers.perform_all() - token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) - - email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token) - notify_email = Config.get([:instance, :notify_email]) - instance_name = Config.get([:instance, :name]) - - assert_email_sent( - from: {instance_name, notify_email}, - to: {user.name, user.email}, - html_body: email.html_body - ) - end - end - - describe "POST /auth/password, with invalid parameters" do - setup do - user = insert(:user) - {:ok, user: user} - end - - test "it returns 404 when user is not found", %{conn: conn, user: user} do - conn = post(conn, "/auth/password?email=nonexisting_#{user.email}") - assert conn.status == 404 - assert conn.resp_body == "" - end - - test "it returns 400 when user is not local", %{conn: conn, user: user} do - {:ok, user} = Repo.update(Changeset.change(user, local: false)) - conn = post(conn, "/auth/password?email=#{user.email}") - assert conn.status == 400 - assert conn.resp_body == "" - end - end - - describe "DELETE /auth/sign_out" do - test "redirect to root page", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> delete("/auth/sign_out") - - assert conn.status == 302 - assert redirected_to(conn) == "/" - end end describe "empty_array, stubs for mastodon api" do