diff options
Diffstat (limited to 'test/web/auth')
| -rw-r--r-- | test/web/auth/auth_test_controller_test.exs | 242 | ||||
| -rw-r--r-- | test/web/auth/basic_auth_test.exs | 46 | ||||
| -rw-r--r-- | test/web/auth/oauth_test_controller_test.exs | 49 | ||||
| -rw-r--r-- | test/web/auth/pleroma_authenticator_test.exs | 48 | ||||
| -rw-r--r-- | test/web/auth/totp_authenticator_test.exs | 51 | 
5 files changed, 387 insertions, 49 deletions
diff --git a/test/web/auth/auth_test_controller_test.exs b/test/web/auth/auth_test_controller_test.exs new file mode 100644 index 000000000..fed52b7f3 --- /dev/null +++ b/test/web/auth/auth_test_controller_test.exs @@ -0,0 +1,242 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Tests.AuthTestControllerTest do +  use Pleroma.Web.ConnCase + +  import Pleroma.Factory + +  describe "do_oauth_check" do +    test "serves with proper OAuth token (fulfilling requested scopes)" do +      %{conn: good_token_conn, user: user} = oauth_access(["read"]) + +      assert %{"user_id" => user.id} == +               good_token_conn +               |> get("/test/authenticated_api/do_oauth_check") +               |> json_response(200) + +      # Unintended usage (:api) — use with :authenticated_api instead +      assert %{"user_id" => user.id} == +               good_token_conn +               |> get("/test/api/do_oauth_check") +               |> json_response(200) +    end + +    test "fails on no token / missing scope(s)" do +      %{conn: bad_token_conn} = oauth_access(["irrelevant_scope"]) + +      bad_token_conn +      |> get("/test/authenticated_api/do_oauth_check") +      |> json_response(403) + +      bad_token_conn +      |> assign(:token, nil) +      |> get("/test/api/do_oauth_check") +      |> json_response(403) +    end +  end + +  describe "fallback_oauth_check" do +    test "serves with proper OAuth token (fulfilling requested scopes)" do +      %{conn: good_token_conn, user: user} = oauth_access(["read"]) + +      assert %{"user_id" => user.id} == +               good_token_conn +               |> get("/test/api/fallback_oauth_check") +               |> json_response(200) + +      # Unintended usage (:authenticated_api) — use with :api instead +      assert %{"user_id" => user.id} == +               good_token_conn +               |> get("/test/authenticated_api/fallback_oauth_check") +               |> json_response(200) +    end + +    test "for :api on public instance, drops :user and renders on no token / missing scope(s)" do +      clear_config([:instance, :public], true) + +      %{conn: bad_token_conn} = oauth_access(["irrelevant_scope"]) + +      assert %{"user_id" => nil} == +               bad_token_conn +               |> get("/test/api/fallback_oauth_check") +               |> json_response(200) + +      assert %{"user_id" => nil} == +               bad_token_conn +               |> assign(:token, nil) +               |> get("/test/api/fallback_oauth_check") +               |> json_response(200) +    end + +    test "for :api on private instance, fails on no token / missing scope(s)" do +      clear_config([:instance, :public], false) + +      %{conn: bad_token_conn} = oauth_access(["irrelevant_scope"]) + +      bad_token_conn +      |> get("/test/api/fallback_oauth_check") +      |> json_response(403) + +      bad_token_conn +      |> assign(:token, nil) +      |> get("/test/api/fallback_oauth_check") +      |> json_response(403) +    end +  end + +  describe "skip_oauth_check" do +    test "for :authenticated_api, serves if :user is set (regardless of token / token scopes)" do +      user = insert(:user) + +      assert %{"user_id" => user.id} == +               build_conn() +               |> assign(:user, user) +               |> get("/test/authenticated_api/skip_oauth_check") +               |> json_response(200) + +      %{conn: bad_token_conn, user: user} = oauth_access(["irrelevant_scope"]) + +      assert %{"user_id" => user.id} == +               bad_token_conn +               |> get("/test/authenticated_api/skip_oauth_check") +               |> json_response(200) +    end + +    test "serves via :api on public instance if :user is not set" do +      clear_config([:instance, :public], true) + +      assert %{"user_id" => nil} == +               build_conn() +               |> get("/test/api/skip_oauth_check") +               |> json_response(200) + +      build_conn() +      |> get("/test/authenticated_api/skip_oauth_check") +      |> json_response(403) +    end + +    test "fails on private instance if :user is not set" do +      clear_config([:instance, :public], false) + +      build_conn() +      |> get("/test/api/skip_oauth_check") +      |> json_response(403) + +      build_conn() +      |> get("/test/authenticated_api/skip_oauth_check") +      |> json_response(403) +    end +  end + +  describe "fallback_oauth_skip_publicity_check" do +    test "serves with proper OAuth token (fulfilling requested scopes)" do +      %{conn: good_token_conn, user: user} = oauth_access(["read"]) + +      assert %{"user_id" => user.id} == +               good_token_conn +               |> get("/test/api/fallback_oauth_skip_publicity_check") +               |> json_response(200) + +      # Unintended usage (:authenticated_api) +      assert %{"user_id" => user.id} == +               good_token_conn +               |> get("/test/authenticated_api/fallback_oauth_skip_publicity_check") +               |> json_response(200) +    end + +    test "for :api on private / public instance, drops :user and renders on token issue" do +      %{conn: bad_token_conn} = oauth_access(["irrelevant_scope"]) + +      for is_public <- [true, false] do +        clear_config([:instance, :public], is_public) + +        assert %{"user_id" => nil} == +                 bad_token_conn +                 |> get("/test/api/fallback_oauth_skip_publicity_check") +                 |> json_response(200) + +        assert %{"user_id" => nil} == +                 bad_token_conn +                 |> assign(:token, nil) +                 |> get("/test/api/fallback_oauth_skip_publicity_check") +                 |> json_response(200) +      end +    end +  end + +  describe "skip_oauth_skip_publicity_check" do +    test "for :authenticated_api, serves if :user is set (regardless of token / token scopes)" do +      user = insert(:user) + +      assert %{"user_id" => user.id} == +               build_conn() +               |> assign(:user, user) +               |> get("/test/authenticated_api/skip_oauth_skip_publicity_check") +               |> json_response(200) + +      %{conn: bad_token_conn, user: user} = oauth_access(["irrelevant_scope"]) + +      assert %{"user_id" => user.id} == +               bad_token_conn +               |> get("/test/authenticated_api/skip_oauth_skip_publicity_check") +               |> json_response(200) +    end + +    test "for :api, serves on private and public instances regardless of whether :user is set" do +      user = insert(:user) + +      for is_public <- [true, false] do +        clear_config([:instance, :public], is_public) + +        assert %{"user_id" => nil} == +                 build_conn() +                 |> get("/test/api/skip_oauth_skip_publicity_check") +                 |> json_response(200) + +        assert %{"user_id" => user.id} == +                 build_conn() +                 |> assign(:user, user) +                 |> get("/test/api/skip_oauth_skip_publicity_check") +                 |> json_response(200) +      end +    end +  end + +  describe "missing_oauth_check_definition" do +    def test_missing_oauth_check_definition_failure(endpoint, expected_error) do +      %{conn: conn} = oauth_access(["read", "write", "follow", "push", "admin"]) + +      assert %{"error" => expected_error} == +               conn +               |> get(endpoint) +               |> json_response(403) +    end + +    test "fails if served via :authenticated_api" do +      test_missing_oauth_check_definition_failure( +        "/test/authenticated_api/missing_oauth_check_definition", +        "Security violation: OAuth scopes check was neither handled nor explicitly skipped." +      ) +    end + +    test "fails if served via :api and the instance is private" do +      clear_config([:instance, :public], false) + +      test_missing_oauth_check_definition_failure( +        "/test/api/missing_oauth_check_definition", +        "This resource requires authentication." +      ) +    end + +    test "succeeds with dropped :user if served via :api on public instance" do +      %{conn: conn} = oauth_access(["read", "write", "follow", "push", "admin"]) + +      assert %{"user_id" => nil} == +               conn +               |> get("/test/api/missing_oauth_check_definition") +               |> json_response(200) +    end +  end +end diff --git a/test/web/auth/basic_auth_test.exs b/test/web/auth/basic_auth_test.exs new file mode 100644 index 000000000..bf6e3d2fc --- /dev/null +++ b/test/web/auth/basic_auth_test.exs @@ -0,0 +1,46 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Auth.BasicAuthTest do +  use Pleroma.Web.ConnCase + +  import Pleroma.Factory + +  test "with HTTP Basic Auth used, grants access to OAuth scope-restricted endpoints", %{ +    conn: conn +  } do +    user = insert(:user) +    assert Pbkdf2.verify_pass("test", user.password_hash) + +    basic_auth_contents = +      (URI.encode_www_form(user.nickname) <> ":" <> URI.encode_www_form("test")) +      |> Base.encode64() + +    # Succeeds with HTTP Basic Auth +    response = +      conn +      |> put_req_header("authorization", "Basic " <> basic_auth_contents) +      |> get("/api/v1/accounts/verify_credentials") +      |> json_response(200) + +    user_nickname = user.nickname +    assert %{"username" => ^user_nickname} = response + +    # Succeeds with a properly scoped OAuth token +    valid_token = insert(:oauth_token, scopes: ["read:accounts"]) + +    conn +    |> put_req_header("authorization", "Bearer #{valid_token.token}") +    |> get("/api/v1/accounts/verify_credentials") +    |> json_response(200) + +    # Fails with a wrong-scoped OAuth token (proof of restriction) +    invalid_token = insert(:oauth_token, scopes: ["read:something"]) + +    conn +    |> put_req_header("authorization", "Bearer #{invalid_token.token}") +    |> get("/api/v1/accounts/verify_credentials") +    |> json_response(403) +  end +end diff --git a/test/web/auth/oauth_test_controller_test.exs b/test/web/auth/oauth_test_controller_test.exs deleted file mode 100644 index a2f6009ac..000000000 --- a/test/web/auth/oauth_test_controller_test.exs +++ /dev/null @@ -1,49 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Tests.OAuthTestControllerTest do -  use Pleroma.Web.ConnCase - -  import Pleroma.Factory - -  setup %{conn: conn} do -    user = insert(:user) -    conn = assign(conn, :user, user) -    %{conn: conn, user: user} -  end - -  test "missed_oauth", %{conn: conn} do -    res = -      conn -      |> get("/test/authenticated_api/missed_oauth") -      |> json_response(403) - -    assert res == -             %{ -               "error" => -                 "Security violation: OAuth scopes check was neither handled nor explicitly skipped." -             } -  end - -  test "skipped_oauth", %{conn: conn} do -    conn -    |> assign(:token, nil) -    |> get("/test/authenticated_api/skipped_oauth") -    |> json_response(200) -  end - -  test "performed_oauth", %{user: user} do -    %{conn: good_token_conn} = oauth_access(["read"], user: user) - -    good_token_conn -    |> get("/test/authenticated_api/performed_oauth") -    |> json_response(200) - -    %{conn: bad_token_conn} = oauth_access(["follow"], user: user) - -    bad_token_conn -    |> get("/test/authenticated_api/performed_oauth") -    |> json_response(403) -  end -end diff --git a/test/web/auth/pleroma_authenticator_test.exs b/test/web/auth/pleroma_authenticator_test.exs new file mode 100644 index 000000000..1ba0dfecc --- /dev/null +++ b/test/web/auth/pleroma_authenticator_test.exs @@ -0,0 +1,48 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 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: Pbkdf2.hash_pwd_salt(password)) +    {:ok, [user: user, name: name, password: password]} +  end + +  test "get_user/authorization", %{name: name, password: password} do +    name = name <> "1" +    user = insert(:user, nickname: name, password_hash: Bcrypt.hash_pwd_salt(password)) + +    params = %{"authorization" => %{"name" => name, "password" => password}} +    res = PleromaAuthenticator.get_user(%Plug.Conn{params: params}) + +    assert {:ok, returned_user} = res +    assert returned_user.id == user.id +    assert "$pbkdf2" <> _ = returned_user.password_hash +  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..84d4cd840 --- /dev/null +++ b/test/web/auth/totp_authenticator_test.exs @@ -0,0 +1,51 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 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(&Pbkdf2.hash_pwd_salt(&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  | 
