diff options
Diffstat (limited to 'test/web')
| -rw-r--r-- | test/web/admin_api/admin_api_controller_test.exs | 33 | ||||
| -rw-r--r-- | test/web/auth/pleroma_authenticator_test.exs | 43 | ||||
| -rw-r--r-- | test/web/auth/totp_authenticator_test.exs | 51 | ||||
| -rw-r--r-- | test/web/oauth/mfa_controller_test.exs | 306 | ||||
| -rw-r--r-- | test/web/oauth/oauth_controller_test.exs | 77 | ||||
| -rw-r--r-- | test/web/pleroma_api/controllers/two_factor_authentication_controller_test.exs | 260 | ||||
| -rw-r--r-- | test/web/twitter_api/remote_follow_controller_test.exs | 116 | 
7 files changed, 886 insertions, 0 deletions
| diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index 7ab7cc15c..4697af50e 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -14,6 +14,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do    alias Pleroma.Config    alias Pleroma.ConfigDB    alias Pleroma.HTML +  alias Pleroma.MFA    alias Pleroma.ModerationLog    alias Pleroma.Repo    alias Pleroma.ReportNote @@ -1278,6 +1279,38 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do               "@#{admin.nickname} deactivated users: @#{user.nickname}"    end +  describe "PUT disable_mfa" do +    test "returns 200 and disable 2fa", %{conn: conn} do +      user = +        insert(:user, +          multi_factor_authentication_settings: %MFA.Settings{ +            enabled: true, +            totp: %MFA.Settings.TOTP{secret: "otp_secret", confirmed: true} +          } +        ) + +      response = +        conn +        |> put("/api/pleroma/admin/users/disable_mfa", %{nickname: user.nickname}) +        |> json_response(200) + +      assert response == user.nickname +      mfa_settings = refresh_record(user).multi_factor_authentication_settings + +      refute mfa_settings.enabled +      refute mfa_settings.totp.confirmed +    end + +    test "returns 404 if user not found", %{conn: conn} do +      response = +        conn +        |> put("/api/pleroma/admin/users/disable_mfa", %{nickname: "nickname"}) +        |> json_response(404) + +      assert response == "Not found" +    end +  end +    describe "POST /api/pleroma/admin/users/invite_token" do      test "without options", %{conn: conn} do        conn = post(conn, "/api/pleroma/admin/users/invite_token") diff --git a/test/web/auth/pleroma_authenticator_test.exs b/test/web/auth/pleroma_authenticator_test.exs new file mode 100644 index 000000000..7125c5081 --- /dev/null +++ b/test/web/auth/pleroma_authenticator_test.exs @@ -0,0 +1,43 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Auth.PleromaAuthenticatorTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Web.Auth.PleromaAuthenticator +  import Pleroma.Factory + +  setup do +    password = "testpassword" +    name = "AgentSmith" +    user = insert(:user, nickname: name, password_hash: Comeonin.Pbkdf2.hashpwsalt(password)) +    {:ok, [user: user, name: name, password: password]} +  end + +  test "get_user/authorization", %{user: user, name: name, password: password} do +    params = %{"authorization" => %{"name" => name, "password" => password}} +    res = PleromaAuthenticator.get_user(%Plug.Conn{params: params}) + +    assert {:ok, user} == res +  end + +  test "get_user/authorization with invalid password", %{name: name} do +    params = %{"authorization" => %{"name" => name, "password" => "password"}} +    res = PleromaAuthenticator.get_user(%Plug.Conn{params: params}) + +    assert {:error, {:checkpw, false}} == res +  end + +  test "get_user/grant_type_password", %{user: user, name: name, password: password} do +    params = %{"grant_type" => "password", "username" => name, "password" => password} +    res = PleromaAuthenticator.get_user(%Plug.Conn{params: params}) + +    assert {:ok, user} == res +  end + +  test "error credintails" do +    res = PleromaAuthenticator.get_user(%Plug.Conn{params: %{}}) +    assert {:error, :invalid_credentials} == res +  end +end diff --git a/test/web/auth/totp_authenticator_test.exs b/test/web/auth/totp_authenticator_test.exs new file mode 100644 index 000000000..e08069490 --- /dev/null +++ b/test/web/auth/totp_authenticator_test.exs @@ -0,0 +1,51 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Auth.TOTPAuthenticatorTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.MFA +  alias Pleroma.MFA.BackupCodes +  alias Pleroma.MFA.TOTP +  alias Pleroma.Web.Auth.TOTPAuthenticator + +  import Pleroma.Factory + +  test "verify token" do +    otp_secret = TOTP.generate_secret() +    otp_token = TOTP.generate_token(otp_secret) + +    user = +      insert(:user, +        multi_factor_authentication_settings: %MFA.Settings{ +          enabled: true, +          totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} +        } +      ) + +    assert TOTPAuthenticator.verify(otp_token, user) == {:ok, :pass} +    assert TOTPAuthenticator.verify(nil, user) == {:error, :invalid_token} +    assert TOTPAuthenticator.verify("", user) == {:error, :invalid_token} +  end + +  test "checks backup codes" do +    [code | _] = backup_codes = BackupCodes.generate() + +    hashed_codes = +      backup_codes +      |> Enum.map(&Comeonin.Pbkdf2.hashpwsalt(&1)) + +    user = +      insert(:user, +        multi_factor_authentication_settings: %MFA.Settings{ +          enabled: true, +          backup_codes: hashed_codes, +          totp: %MFA.Settings.TOTP{secret: "otp_secret", confirmed: true} +        } +      ) + +    assert TOTPAuthenticator.verify_recovery_code(user, code) == {:ok, :pass} +    refute TOTPAuthenticator.verify_recovery_code(code, refresh_record(user)) == {:ok, :pass} +  end +end diff --git a/test/web/oauth/mfa_controller_test.exs b/test/web/oauth/mfa_controller_test.exs new file mode 100644 index 000000000..ce4a07320 --- /dev/null +++ b/test/web/oauth/mfa_controller_test.exs @@ -0,0 +1,306 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.MFAControllerTest do +  use Pleroma.Web.ConnCase +  import Pleroma.Factory + +  alias Pleroma.MFA +  alias Pleroma.MFA.BackupCodes +  alias Pleroma.MFA.TOTP +  alias Pleroma.Repo +  alias Pleroma.Web.OAuth.Authorization +  alias Pleroma.Web.OAuth.OAuthController + +  setup %{conn: conn} do +    otp_secret = TOTP.generate_secret() + +    user = +      insert(:user, +        multi_factor_authentication_settings: %MFA.Settings{ +          enabled: true, +          backup_codes: [Comeonin.Pbkdf2.hashpwsalt("test-code")], +          totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} +        } +      ) + +    app = insert(:oauth_app) +    {:ok, conn: conn, user: user, app: app} +  end + +  describe "show" do +    setup %{conn: conn, user: user, app: app} do +      mfa_token = +        insert(:mfa_token, +          user: user, +          authorization: build(:oauth_authorization, app: app, scopes: ["write"]) +        ) + +      {:ok, conn: conn, mfa_token: mfa_token} +    end + +    test "GET /oauth/mfa renders mfa forms", %{conn: conn, mfa_token: mfa_token} do +      conn = +        get( +          conn, +          "/oauth/mfa", +          %{ +            "mfa_token" => mfa_token.token, +            "state" => "a_state", +            "redirect_uri" => "http://localhost:8080/callback" +          } +        ) + +      assert response = html_response(conn, 200) +      assert response =~ "Two-factor authentication" +      assert response =~ mfa_token.token +      assert response =~ "http://localhost:8080/callback" +    end + +    test "GET /oauth/mfa renders mfa recovery forms", %{conn: conn, mfa_token: mfa_token} do +      conn = +        get( +          conn, +          "/oauth/mfa", +          %{ +            "mfa_token" => mfa_token.token, +            "state" => "a_state", +            "redirect_uri" => "http://localhost:8080/callback", +            "challenge_type" => "recovery" +          } +        ) + +      assert response = html_response(conn, 200) +      assert response =~ "Two-factor recovery" +      assert response =~ mfa_token.token +      assert response =~ "http://localhost:8080/callback" +    end +  end + +  describe "verify" do +    setup %{conn: conn, user: user, app: app} do +      mfa_token = +        insert(:mfa_token, +          user: user, +          authorization: build(:oauth_authorization, app: app, scopes: ["write"]) +        ) + +      {:ok, conn: conn, user: user, mfa_token: mfa_token, app: app} +    end + +    test "POST /oauth/mfa/verify, verify totp code", %{ +      conn: conn, +      user: user, +      mfa_token: mfa_token, +      app: app +    } do +      otp_token = TOTP.generate_token(user.multi_factor_authentication_settings.totp.secret) + +      conn = +        conn +        |> post("/oauth/mfa/verify", %{ +          "mfa" => %{ +            "mfa_token" => mfa_token.token, +            "challenge_type" => "totp", +            "code" => otp_token, +            "state" => "a_state", +            "redirect_uri" => OAuthController.default_redirect_uri(app) +          } +        }) + +      target = redirected_to(conn) +      target_url = %URI{URI.parse(target) | query: nil} |> URI.to_string() +      query = URI.parse(target).query |> URI.query_decoder() |> Map.new() +      assert %{"state" => "a_state", "code" => code} = query +      assert target_url == OAuthController.default_redirect_uri(app) +      auth = Repo.get_by(Authorization, token: code) +      assert auth.scopes == ["write"] +    end + +    test "POST /oauth/mfa/verify, verify recovery code", %{ +      conn: conn, +      mfa_token: mfa_token, +      app: app +    } do +      conn = +        conn +        |> post("/oauth/mfa/verify", %{ +          "mfa" => %{ +            "mfa_token" => mfa_token.token, +            "challenge_type" => "recovery", +            "code" => "test-code", +            "state" => "a_state", +            "redirect_uri" => OAuthController.default_redirect_uri(app) +          } +        }) + +      target = redirected_to(conn) +      target_url = %URI{URI.parse(target) | query: nil} |> URI.to_string() +      query = URI.parse(target).query |> URI.query_decoder() |> Map.new() +      assert %{"state" => "a_state", "code" => code} = query +      assert target_url == OAuthController.default_redirect_uri(app) +      auth = Repo.get_by(Authorization, token: code) +      assert auth.scopes == ["write"] +    end +  end + +  describe "challenge/totp" do +    test "returns access token with valid code", %{conn: conn, user: user, app: app} do +      otp_token = TOTP.generate_token(user.multi_factor_authentication_settings.totp.secret) + +      mfa_token = +        insert(:mfa_token, +          user: user, +          authorization: build(:oauth_authorization, app: app, scopes: ["write"]) +        ) + +      response = +        conn +        |> post("/oauth/mfa/challenge", %{ +          "mfa_token" => mfa_token.token, +          "challenge_type" => "totp", +          "code" => otp_token, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) +        |> json_response(:ok) + +      ap_id = user.ap_id + +      assert match?( +               %{ +                 "access_token" => _, +                 "expires_in" => 600, +                 "me" => ^ap_id, +                 "refresh_token" => _, +                 "scope" => "write", +                 "token_type" => "Bearer" +               }, +               response +             ) +    end + +    test "returns errors when mfa token invalid", %{conn: conn, user: user, app: app} do +      otp_token = TOTP.generate_token(user.multi_factor_authentication_settings.totp.secret) + +      response = +        conn +        |> post("/oauth/mfa/challenge", %{ +          "mfa_token" => "XXX", +          "challenge_type" => "totp", +          "code" => otp_token, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) +        |> json_response(400) + +      assert response == %{"error" => "Invalid code"} +    end + +    test "returns error when otp code is invalid", %{conn: conn, user: user, app: app} do +      mfa_token = insert(:mfa_token, user: user) + +      response = +        conn +        |> post("/oauth/mfa/challenge", %{ +          "mfa_token" => mfa_token.token, +          "challenge_type" => "totp", +          "code" => "XXX", +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) +        |> json_response(400) + +      assert response == %{"error" => "Invalid code"} +    end + +    test "returns error when client credentails is wrong ", %{conn: conn, user: user} do +      otp_token = TOTP.generate_token(user.multi_factor_authentication_settings.totp.secret) +      mfa_token = insert(:mfa_token, user: user) + +      response = +        conn +        |> post("/oauth/mfa/challenge", %{ +          "mfa_token" => mfa_token.token, +          "challenge_type" => "totp", +          "code" => otp_token, +          "client_id" => "xxx", +          "client_secret" => "xxx" +        }) +        |> json_response(400) + +      assert response == %{"error" => "Invalid code"} +    end +  end + +  describe "challenge/recovery" do +    setup %{conn: conn} do +      app = insert(:oauth_app) +      {:ok, conn: conn, app: app} +    end + +    test "returns access token with valid code", %{conn: conn, app: app} do +      otp_secret = TOTP.generate_secret() + +      [code | _] = backup_codes = BackupCodes.generate() + +      hashed_codes = +        backup_codes +        |> Enum.map(&Comeonin.Pbkdf2.hashpwsalt(&1)) + +      user = +        insert(:user, +          multi_factor_authentication_settings: %MFA.Settings{ +            enabled: true, +            backup_codes: hashed_codes, +            totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} +          } +        ) + +      mfa_token = +        insert(:mfa_token, +          user: user, +          authorization: build(:oauth_authorization, app: app, scopes: ["write"]) +        ) + +      response = +        conn +        |> post("/oauth/mfa/challenge", %{ +          "mfa_token" => mfa_token.token, +          "challenge_type" => "recovery", +          "code" => code, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) +        |> json_response(:ok) + +      ap_id = user.ap_id + +      assert match?( +               %{ +                 "access_token" => _, +                 "expires_in" => 600, +                 "me" => ^ap_id, +                 "refresh_token" => _, +                 "scope" => "write", +                 "token_type" => "Bearer" +               }, +               response +             ) + +      error_response = +        conn +        |> post("/oauth/mfa/challenge", %{ +          "mfa_token" => mfa_token.token, +          "challenge_type" => "recovery", +          "code" => code, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) +        |> json_response(400) + +      assert error_response == %{"error" => "Invalid code"} +    end +  end +end diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs index f2f98d768..7a107584d 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/web/oauth/oauth_controller_test.exs @@ -6,6 +6,8 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do    use Pleroma.Web.ConnCase    import Pleroma.Factory +  alias Pleroma.MFA +  alias Pleroma.MFA.TOTP    alias Pleroma.Repo    alias Pleroma.User    alias Pleroma.Web.OAuth.Authorization @@ -604,6 +606,41 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do        end      end +    test "redirect to on two-factor auth page" do +      otp_secret = TOTP.generate_secret() + +      user = +        insert(:user, +          multi_factor_authentication_settings: %MFA.Settings{ +            enabled: true, +            totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} +          } +        ) + +      app = insert(:oauth_app, scopes: ["read", "write", "follow"]) + +      conn = +        build_conn() +        |> post("/oauth/authorize", %{ +          "authorization" => %{ +            "name" => user.nickname, +            "password" => "test", +            "client_id" => app.client_id, +            "redirect_uri" => app.redirect_uris, +            "scope" => "read write", +            "state" => "statepassed" +          } +        }) + +      result = html_response(conn, 200) + +      mfa_token = Repo.get_by(MFA.Token, user_id: user.id) +      assert result =~ app.redirect_uris +      assert result =~ "statepassed" +      assert result =~ mfa_token.token +      assert result =~ "Two-factor authentication" +    end +      test "returns 401 for wrong credentials", %{conn: conn} do        user = insert(:user)        app = insert(:oauth_app) @@ -735,6 +772,46 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do        assert token.scopes == app.scopes      end +    test "issues a mfa token for `password` grant_type, when MFA enabled" do +      password = "testpassword" +      otp_secret = TOTP.generate_secret() + +      user = +        insert(:user, +          password_hash: Comeonin.Pbkdf2.hashpwsalt(password), +          multi_factor_authentication_settings: %MFA.Settings{ +            enabled: true, +            totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} +          } +        ) + +      app = insert(:oauth_app, scopes: ["read", "write"]) + +      response = +        build_conn() +        |> post("/oauth/token", %{ +          "grant_type" => "password", +          "username" => user.nickname, +          "password" => password, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) +        |> json_response(403) + +      assert match?( +               %{ +                 "supported_challenge_types" => "totp", +                 "mfa_token" => _, +                 "error" => "mfa_required" +               }, +               response +             ) + +      token = Repo.get_by(MFA.Token, token: response["mfa_token"]) +      assert token.user_id == user.id +      assert token.authorization_id +    end +      test "issues a token for request with HTTP basic auth client credentials" do        user = insert(:user)        app = insert(:oauth_app, scopes: ["scope1", "scope2", "scope3"]) diff --git a/test/web/pleroma_api/controllers/two_factor_authentication_controller_test.exs b/test/web/pleroma_api/controllers/two_factor_authentication_controller_test.exs new file mode 100644 index 000000000..d23d08a00 --- /dev/null +++ b/test/web/pleroma_api/controllers/two_factor_authentication_controller_test.exs @@ -0,0 +1,260 @@ +defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationControllerTest do +  use Pleroma.Web.ConnCase + +  import Pleroma.Factory +  alias Pleroma.MFA.Settings +  alias Pleroma.MFA.TOTP + +  describe "GET /api/pleroma/accounts/mfa/settings" do +    test "returns user mfa settings for new user", %{conn: conn} do +      token = insert(:oauth_token, scopes: ["read", "follow"]) +      token2 = insert(:oauth_token, scopes: ["write"]) + +      assert conn +             |> put_req_header("authorization", "Bearer #{token.token}") +             |> get("/api/pleroma/accounts/mfa") +             |> json_response(:ok) == %{ +               "settings" => %{"enabled" => false, "totp" => false} +             } + +      assert conn +             |> put_req_header("authorization", "Bearer #{token2.token}") +             |> get("/api/pleroma/accounts/mfa") +             |> json_response(403) == %{ +               "error" => "Insufficient permissions: read:security." +             } +    end + +    test "returns user mfa settings with enabled totp", %{conn: conn} do +      user = +        insert(:user, +          multi_factor_authentication_settings: %Settings{ +            enabled: true, +            totp: %Settings.TOTP{secret: "XXX", delivery_type: "app", confirmed: true} +          } +        ) + +      token = insert(:oauth_token, scopes: ["read", "follow"], user: user) + +      assert conn +             |> put_req_header("authorization", "Bearer #{token.token}") +             |> get("/api/pleroma/accounts/mfa") +             |> json_response(:ok) == %{ +               "settings" => %{"enabled" => true, "totp" => true} +             } +    end +  end + +  describe "GET /api/pleroma/accounts/mfa/backup_codes" do +    test "returns backup codes", %{conn: conn} do +      user = +        insert(:user, +          multi_factor_authentication_settings: %Settings{ +            backup_codes: ["1", "2", "3"], +            totp: %Settings.TOTP{secret: "secret"} +          } +        ) + +      token = insert(:oauth_token, scopes: ["write", "follow"], user: user) +      token2 = insert(:oauth_token, scopes: ["read"]) + +      response = +        conn +        |> put_req_header("authorization", "Bearer #{token.token}") +        |> get("/api/pleroma/accounts/mfa/backup_codes") +        |> json_response(:ok) + +      assert [<<_::bytes-size(6)>>, <<_::bytes-size(6)>>] = response["codes"] +      user = refresh_record(user) +      mfa_settings = user.multi_factor_authentication_settings +      assert mfa_settings.totp.secret == "secret" +      refute mfa_settings.backup_codes == ["1", "2", "3"] +      refute mfa_settings.backup_codes == [] + +      assert conn +             |> put_req_header("authorization", "Bearer #{token2.token}") +             |> get("/api/pleroma/accounts/mfa/backup_codes") +             |> json_response(403) == %{ +               "error" => "Insufficient permissions: write:security." +             } +    end +  end + +  describe "GET /api/pleroma/accounts/mfa/setup/totp" do +    test "return errors when method is invalid", %{conn: conn} do +      user = insert(:user) +      token = insert(:oauth_token, scopes: ["write", "follow"], user: user) + +      response = +        conn +        |> put_req_header("authorization", "Bearer #{token.token}") +        |> get("/api/pleroma/accounts/mfa/setup/torf") +        |> json_response(400) + +      assert response == %{"error" => "undefined method"} +    end + +    test "returns key and provisioning_uri", %{conn: conn} do +      user = +        insert(:user, +          multi_factor_authentication_settings: %Settings{backup_codes: ["1", "2", "3"]} +        ) + +      token = insert(:oauth_token, scopes: ["write", "follow"], user: user) +      token2 = insert(:oauth_token, scopes: ["read"]) + +      response = +        conn +        |> put_req_header("authorization", "Bearer #{token.token}") +        |> get("/api/pleroma/accounts/mfa/setup/totp") +        |> json_response(:ok) + +      user = refresh_record(user) +      mfa_settings = user.multi_factor_authentication_settings +      secret = mfa_settings.totp.secret +      refute mfa_settings.enabled +      assert mfa_settings.backup_codes == ["1", "2", "3"] + +      assert response == %{ +               "key" => secret, +               "provisioning_uri" => TOTP.provisioning_uri(secret, "#{user.email}") +             } + +      assert conn +             |> put_req_header("authorization", "Bearer #{token2.token}") +             |> get("/api/pleroma/accounts/mfa/setup/totp") +             |> json_response(403) == %{ +               "error" => "Insufficient permissions: write:security." +             } +    end +  end + +  describe "GET /api/pleroma/accounts/mfa/confirm/totp" do +    test "returns success result", %{conn: conn} do +      secret = TOTP.generate_secret() +      code = TOTP.generate_token(secret) + +      user = +        insert(:user, +          multi_factor_authentication_settings: %Settings{ +            backup_codes: ["1", "2", "3"], +            totp: %Settings.TOTP{secret: secret} +          } +        ) + +      token = insert(:oauth_token, scopes: ["write", "follow"], user: user) +      token2 = insert(:oauth_token, scopes: ["read"]) + +      assert conn +             |> put_req_header("authorization", "Bearer #{token.token}") +             |> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: code}) +             |> json_response(:ok) + +      settings = refresh_record(user).multi_factor_authentication_settings +      assert settings.enabled +      assert settings.totp.secret == secret +      assert settings.totp.confirmed +      assert settings.backup_codes == ["1", "2", "3"] + +      assert conn +             |> put_req_header("authorization", "Bearer #{token2.token}") +             |> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: code}) +             |> json_response(403) == %{ +               "error" => "Insufficient permissions: write:security." +             } +    end + +    test "returns error if password incorrect", %{conn: conn} do +      secret = TOTP.generate_secret() +      code = TOTP.generate_token(secret) + +      user = +        insert(:user, +          multi_factor_authentication_settings: %Settings{ +            backup_codes: ["1", "2", "3"], +            totp: %Settings.TOTP{secret: secret} +          } +        ) + +      token = insert(:oauth_token, scopes: ["write", "follow"], user: user) + +      response = +        conn +        |> put_req_header("authorization", "Bearer #{token.token}") +        |> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "xxx", code: code}) +        |> json_response(422) + +      settings = refresh_record(user).multi_factor_authentication_settings +      refute settings.enabled +      refute settings.totp.confirmed +      assert settings.backup_codes == ["1", "2", "3"] +      assert response == %{"error" => "Invalid password."} +    end + +    test "returns error if code incorrect", %{conn: conn} do +      secret = TOTP.generate_secret() + +      user = +        insert(:user, +          multi_factor_authentication_settings: %Settings{ +            backup_codes: ["1", "2", "3"], +            totp: %Settings.TOTP{secret: secret} +          } +        ) + +      token = insert(:oauth_token, scopes: ["write", "follow"], user: user) +      token2 = insert(:oauth_token, scopes: ["read"]) + +      response = +        conn +        |> put_req_header("authorization", "Bearer #{token.token}") +        |> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: "code"}) +        |> json_response(422) + +      settings = refresh_record(user).multi_factor_authentication_settings +      refute settings.enabled +      refute settings.totp.confirmed +      assert settings.backup_codes == ["1", "2", "3"] +      assert response == %{"error" => "invalid_token"} + +      assert conn +             |> put_req_header("authorization", "Bearer #{token2.token}") +             |> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: "code"}) +             |> json_response(403) == %{ +               "error" => "Insufficient permissions: write:security." +             } +    end +  end + +  describe "DELETE /api/pleroma/accounts/mfa/totp" do +    test "returns success result", %{conn: conn} do +      user = +        insert(:user, +          multi_factor_authentication_settings: %Settings{ +            backup_codes: ["1", "2", "3"], +            totp: %Settings.TOTP{secret: "secret"} +          } +        ) + +      token = insert(:oauth_token, scopes: ["write", "follow"], user: user) +      token2 = insert(:oauth_token, scopes: ["read"]) + +      assert conn +             |> put_req_header("authorization", "Bearer #{token.token}") +             |> delete("/api/pleroma/accounts/mfa/totp", %{password: "test"}) +             |> json_response(:ok) + +      settings = refresh_record(user).multi_factor_authentication_settings +      refute settings.enabled +      assert settings.totp.secret == nil +      refute settings.totp.confirmed + +      assert conn +             |> put_req_header("authorization", "Bearer #{token2.token}") +             |> delete("/api/pleroma/accounts/mfa/totp", %{password: "test"}) +             |> json_response(403) == %{ +               "error" => "Insufficient permissions: write:security." +             } +    end +  end +end diff --git a/test/web/twitter_api/remote_follow_controller_test.exs b/test/web/twitter_api/remote_follow_controller_test.exs index 5ff8694a8..f7e54c26a 100644 --- a/test/web/twitter_api/remote_follow_controller_test.exs +++ b/test/web/twitter_api/remote_follow_controller_test.exs @@ -6,11 +6,14 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do    use Pleroma.Web.ConnCase    alias Pleroma.Config +  alias Pleroma.MFA +  alias Pleroma.MFA.TOTP    alias Pleroma.User    alias Pleroma.Web.CommonAPI    import ExUnit.CaptureLog    import Pleroma.Factory +  import Ecto.Query    setup do      Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -160,6 +163,119 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do      end    end +  describe "POST /ostatus_subscribe - follow/2 with enabled Two-Factor Auth " do +    test "render the MFA login form", %{conn: conn} do +      otp_secret = TOTP.generate_secret() + +      user = +        insert(:user, +          multi_factor_authentication_settings: %MFA.Settings{ +            enabled: true, +            totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} +          } +        ) + +      user2 = insert(:user) + +      response = +        conn +        |> post(remote_follow_path(conn, :do_follow), %{ +          "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} +        }) +        |> response(200) + +      mfa_token = Pleroma.Repo.one(from(q in Pleroma.MFA.Token, where: q.user_id == ^user.id)) + +      assert response =~ "Two-factor authentication" +      assert response =~ "Authentication code" +      assert response =~ mfa_token.token +      refute user2.follower_address in User.following(user) +    end + +    test "returns error when password is incorrect", %{conn: conn} do +      otp_secret = TOTP.generate_secret() + +      user = +        insert(:user, +          multi_factor_authentication_settings: %MFA.Settings{ +            enabled: true, +            totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} +          } +        ) + +      user2 = insert(:user) + +      response = +        conn +        |> post(remote_follow_path(conn, :do_follow), %{ +          "authorization" => %{"name" => user.nickname, "password" => "test1", "id" => user2.id} +        }) +        |> response(200) + +      assert response =~ "Wrong username or password" +      refute user2.follower_address in User.following(user) +    end + +    test "follows", %{conn: conn} do +      otp_secret = TOTP.generate_secret() + +      user = +        insert(:user, +          multi_factor_authentication_settings: %MFA.Settings{ +            enabled: true, +            totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} +          } +        ) + +      {:ok, %{token: token}} = MFA.Token.create_token(user) + +      user2 = insert(:user) +      otp_token = TOTP.generate_token(otp_secret) + +      conn = +        conn +        |> post( +          remote_follow_path(conn, :do_follow), +          %{ +            "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id} +          } +        ) + +      assert redirected_to(conn) == "/users/#{user2.id}" +      assert user2.follower_address in User.following(user) +    end + +    test "returns error when auth code is incorrect", %{conn: conn} do +      otp_secret = TOTP.generate_secret() + +      user = +        insert(:user, +          multi_factor_authentication_settings: %MFA.Settings{ +            enabled: true, +            totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} +          } +        ) + +      {:ok, %{token: token}} = MFA.Token.create_token(user) + +      user2 = insert(:user) +      otp_token = TOTP.generate_token(TOTP.generate_secret()) + +      response = +        conn +        |> post( +          remote_follow_path(conn, :do_follow), +          %{ +            "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id} +          } +        ) +        |> response(200) + +      assert response =~ "Wrong authentication code" +      refute user2.follower_address in User.following(user) +    end +  end +    describe "POST /ostatus_subscribe - follow/2 without assigned user " do      test "follows", %{conn: conn} do        user = insert(:user) | 
