Fix oauth2 (for real) (#179)
[akkoma] / test / pleroma / web / o_auth / o_auth_controller_test.exs
index 1200126b81dca7b5b45be13b95447dc8eed6d3a2..9f984b26fad02a21af581c362443bea9e0a84ea5 100644 (file)
@@ -1,9 +1,10 @@
 # Pleroma: A lightweight social networking server
 # Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.OAuth.OAuthControllerTest do
   use Pleroma.Web.ConnCase
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.OAuth.OAuthControllerTest do
   use Pleroma.Web.ConnCase
+
   import Pleroma.Factory
 
   alias Pleroma.MFA
   import Pleroma.Factory
 
   alias Pleroma.MFA
@@ -77,11 +78,11 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
           }
         )
 
           }
         )
 
-      assert response = html_response(conn, 302)
+      assert html_response(conn, 302)
 
       redirect_query = URI.parse(redirected_to(conn)).query
       assert %{"state" => state_param} = URI.decode_query(redirect_query)
 
       redirect_query = URI.parse(redirected_to(conn)).query
       assert %{"state" => state_param} = URI.decode_query(redirect_query)
-      assert {:ok, state_components} = Poison.decode(state_param)
+      assert {:ok, state_components} = Jason.decode(state_param)
 
       expected_client_id = app.client_id
       expected_redirect_uri = app.redirect_uris
 
       expected_client_id = app.client_id
       expected_redirect_uri = app.redirect_uris
@@ -115,11 +116,11 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
             "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
             "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
             "provider" => "twitter",
             "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
             "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
             "provider" => "twitter",
-            "state" => Poison.encode!(state_params)
+            "state" => Jason.encode!(state_params)
           }
         )
 
           }
         )
 
-      assert response = html_response(conn, 302)
+      assert html_response(conn, 302)
       assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
     end
 
       assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
     end
 
@@ -147,7 +148,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
             "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
             "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
             "provider" => "twitter",
             "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
             "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
             "provider" => "twitter",
-            "state" => Poison.encode!(state_params)
+            "state" => Jason.encode!(state_params)
           }
         )
 
           }
         )
 
@@ -178,11 +179,11 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
             "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
             "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
             "provider" => "twitter",
             "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
             "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
             "provider" => "twitter",
-            "state" => Poison.encode!(state_params)
+            "state" => Jason.encode!(state_params)
           }
         )
 
           }
         )
 
-      assert response = html_response(conn, 302)
+      assert html_response(conn, 302)
       assert redirected_to(conn) == app.redirect_uris
       assert get_flash(conn, :error) == "Failed to authenticate: (error description)."
     end
       assert redirected_to(conn) == app.redirect_uris
       assert get_flash(conn, :error) == "Failed to authenticate: (error description)."
     end
@@ -238,7 +239,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
           }
         )
 
           }
         )
 
-      assert response = html_response(conn, 302)
+      assert html_response(conn, 302)
       assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
     end
 
       assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
     end
 
@@ -268,7 +269,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
           }
         )
 
           }
         )
 
-      assert response = html_response(conn, 401)
+      assert html_response(conn, 401)
     end
 
     test "with invalid params, POST /oauth/register?op=register renders registration_details page",
     end
 
     test "with invalid params, POST /oauth/register?op=register renders registration_details page",
@@ -314,7 +315,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
            app: app,
            conn: conn
          } do
            app: app,
            conn: conn
          } do
-      user = insert(:user, password_hash: Pbkdf2.hash_pwd_salt("testpassword"))
+      user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("testpassword"))
       registration = insert(:registration, user: nil)
       redirect_uri = OAuthController.default_redirect_uri(app)
 
       registration = insert(:registration, user: nil)
       redirect_uri = OAuthController.default_redirect_uri(app)
 
@@ -336,7 +337,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
           }
         )
 
           }
         )
 
-      assert response = html_response(conn, 302)
+      assert html_response(conn, 302)
       assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
     end
 
       assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
     end
 
@@ -345,7 +346,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
            app: app,
            conn: conn
          } do
            app: app,
            conn: conn
          } do
-      user = insert(:user, password_hash: Pbkdf2.hash_pwd_salt("testpassword"))
+      user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("testpassword"))
       registration = insert(:registration, user: nil)
       unlisted_redirect_uri = "http://cross-site-request.com"
 
       registration = insert(:registration, user: nil)
       unlisted_redirect_uri = "http://cross-site-request.com"
 
@@ -367,7 +368,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
           }
         )
 
           }
         )
 
-      assert response = html_response(conn, 401)
+      assert html_response(conn, 401)
     end
 
     test "with invalid params, POST /oauth/register?op=connect renders registration_details page",
     end
 
     test "with invalid params, POST /oauth/register?op=connect renders registration_details page",
@@ -454,7 +455,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       conn =
         conn
 
       conn =
         conn
-        |> put_session(:oauth_token, token.token)
+        |> put_req_header("authorization", "Bearer #{token.token}")
         |> get(
           "/oauth/authorize",
           %{
         |> get(
           "/oauth/authorize",
           %{
@@ -469,22 +470,92 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
       assert html_response(conn, 200) =~ ~s(type="submit")
     end
 
       assert html_response(conn, 200) =~ ~s(type="submit")
     end
 
-    test "renders authentication page if user is already authenticated but user request with another client",
+    test "reuses authentication if the user is authenticated with another client",
          %{
          %{
-           app: app,
            conn: conn
          } do
            conn: conn
          } do
-      token = insert(:oauth_token, app: app)
+      user = insert(:user)
+
+      app = insert(:oauth_app, redirect_uris: "https://redirect.url")
+      other_app = insert(:oauth_app, redirect_uris: "https://redirect.url")
+
+      token = insert(:oauth_token, user: user, app: app)
+      reusable_token = insert(:oauth_token, app: other_app, user: user)
 
       conn =
         conn
 
       conn =
         conn
-        |> put_session(:oauth_token, token.token)
+        |> put_req_header("authorization", "Bearer #{token.token}")
         |> get(
           "/oauth/authorize",
           %{
             "response_type" => "code",
         |> get(
           "/oauth/authorize",
           %{
             "response_type" => "code",
-            "client_id" => "another_client_id",
-            "redirect_uri" => OAuthController.default_redirect_uri(app),
+            "client_id" => other_app.client_id,
+            "redirect_uri" => OAuthController.default_redirect_uri(other_app),
+            "scope" => "read"
+          }
+        )
+
+      assert URI.decode(redirected_to(conn)) ==
+               "https://redirect.url?access_token=#{reusable_token.token}"
+    end
+
+    test "does not reuse other people's tokens",
+         %{
+           conn: conn
+         } do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      app = insert(:oauth_app, redirect_uris: "https://redirect.url")
+      other_app = insert(:oauth_app, redirect_uris: "https://redirect.url")
+
+      token = insert(:oauth_token, user: user, app: app)
+      _not_reusable_token = insert(:oauth_token, app: other_app, user: other_user)
+
+      conn =
+        conn
+        |> put_req_header("authorization", "Bearer #{token.token}")
+        |> get(
+          "/oauth/authorize",
+          %{
+            "response_type" => "code",
+            "client_id" => other_app.client_id,
+            "redirect_uri" => OAuthController.default_redirect_uri(other_app),
+            "scope" => "read"
+          }
+        )
+
+      assert html_response(conn, 200) =~ ~s(type="submit")
+    end
+
+    test "does not reuse expired tokens",
+         %{
+           conn: conn
+         } do
+      user = insert(:user)
+
+      app = insert(:oauth_app, redirect_uris: "https://redirect.url")
+
+      other_app = insert(:oauth_app, redirect_uris: "https://redirect.url")
+
+      token = insert(:oauth_token, user: user, app: app)
+
+      _not_reusable_token =
+        insert(:oauth_token,
+          app: other_app,
+          user: user,
+          valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), -60 * 100)
+        )
+
+      conn =
+        conn
+        |> put_req_header("authorization", "Bearer #{token.token}")
+        |> get(
+          "/oauth/authorize",
+          %{
+            "response_type" => "code",
+            "client_id" => other_app.client_id,
+            "redirect_uri" => OAuthController.default_redirect_uri(other_app),
             "scope" => "read"
           }
         )
             "scope" => "read"
           }
         )
@@ -492,6 +563,40 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
       assert html_response(conn, 200) =~ ~s(type="submit")
     end
 
       assert html_response(conn, 200) =~ ~s(type="submit")
     end
 
+    test "does not reuse tokens with the wrong scopes",
+         %{
+           conn: conn
+         } do
+      user = insert(:user)
+
+      app = insert(:oauth_app, redirect_uris: "https://redirect.url")
+
+      other_app = insert(:oauth_app, redirect_uris: "https://redirect.url")
+
+      token = insert(:oauth_token, user: user, app: app, scopes: ["read"])
+
+      _not_reusable_token =
+        insert(:oauth_token,
+          app: other_app,
+          user: user
+        )
+
+      conn =
+        conn
+        |> put_req_header("authorization", "Bearer #{token.token}")
+        |> get(
+          "/oauth/authorize",
+          %{
+            "response_type" => "code",
+            "client_id" => other_app.client_id,
+            "redirect_uri" => OAuthController.default_redirect_uri(other_app),
+            "scope" => "read write"
+          }
+        )
+
+      assert html_response(conn, 200) =~ ~s(type="submit")
+    end
+
     test "with existing authentication and non-OOB `redirect_uri`, redirects to app with `token` and `state` params",
          %{
            app: app,
     test "with existing authentication and non-OOB `redirect_uri`, redirects to app with `token` and `state` params",
          %{
            app: app,
@@ -501,7 +606,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       conn =
         conn
 
       conn =
         conn
-        |> put_session(:oauth_token, token.token)
+        |> put_req_header("authorization", "Bearer #{token.token}")
         |> get(
           "/oauth/authorize",
           %{
         |> get(
           "/oauth/authorize",
           %{
@@ -527,7 +632,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       conn =
         conn
 
       conn =
         conn
-        |> put_session(:oauth_token, token.token)
+        |> put_req_header("authorization", "Bearer #{token.token}")
         |> get(
           "/oauth/authorize",
           %{
         |> get(
           "/oauth/authorize",
           %{
@@ -551,7 +656,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       conn =
         conn
 
       conn =
         conn
-        |> put_session(:oauth_token, token.token)
+        |> put_req_header("authorization", "Bearer #{token.token}")
         |> get(
           "/oauth/authorize",
           %{
         |> get(
           "/oauth/authorize",
           %{
@@ -753,7 +858,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
     test "issues a token for `password` grant_type with valid credentials, with full permissions by default" do
       password = "testpassword"
 
     test "issues a token for `password` grant_type with valid credentials, with full permissions by default" do
       password = "testpassword"
-      user = insert(:user, password_hash: Pbkdf2.hash_pwd_salt(password))
+      user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
 
       app = insert(:oauth_app, scopes: ["read", "write"])
 
 
       app = insert(:oauth_app, scopes: ["read", "write"])
 
@@ -768,10 +873,12 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
           "client_secret" => app.client_secret
         })
 
           "client_secret" => app.client_secret
         })
 
-      assert %{"access_token" => token} = json_response(conn, 200)
+      assert %{"id" => id, "access_token" => access_token} = json_response(conn, 200)
 
 
-      token = Repo.get_by(Token, token: token)
+      token = Repo.get_by(Token, token: access_token)
       assert token
       assert token
+      assert token.id == id
+      assert token.token == access_token
       assert token.scopes == app.scopes
     end
 
       assert token.scopes == app.scopes
     end
 
@@ -781,7 +888,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       user =
         insert(:user,
 
       user =
         insert(:user,
-          password_hash: Pbkdf2.hash_pwd_salt(password),
+          password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password),
           multi_factor_authentication_settings: %MFA.Settings{
             enabled: true,
             totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
           multi_factor_authentication_settings: %MFA.Settings{
             enabled: true,
             totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
@@ -886,12 +993,12 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
     end
 
     test "rejects token exchange for valid credentials belonging to unconfirmed user and confirmation is required" do
     end
 
     test "rejects token exchange for valid credentials belonging to unconfirmed user and confirmation is required" do
-      Pleroma.Config.put([:instance, :account_activation_required], true)
+      clear_config([:instance, :account_activation_required], true)
       password = "testpassword"
 
       {:ok, user} =
       password = "testpassword"
 
       {:ok, user} =
-        insert(:user, password_hash: Pbkdf2.hash_pwd_salt(password))
-        |> User.confirmation_changeset(need_confirmation: true)
+        insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
+        |> User.confirmation_changeset(set_confirmation: false)
         |> User.update_and_set_cache()
 
       refute Pleroma.User.account_status(user) == :active
         |> User.update_and_set_cache()
 
       refute Pleroma.User.account_status(user) == :active
@@ -918,8 +1025,8 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       user =
         insert(:user,
 
       user =
         insert(:user,
-          password_hash: Pbkdf2.hash_pwd_salt(password),
-          deactivated: true
+          password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password),
+          is_active: false
         )
 
       app = insert(:oauth_app)
         )
 
       app = insert(:oauth_app)
@@ -946,7 +1053,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       user =
         insert(:user,
 
       user =
         insert(:user,
-          password_hash: Pbkdf2.hash_pwd_salt(password),
+          password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password),
           password_reset_pending: true
         )
 
           password_reset_pending: true
         )
 
@@ -970,13 +1077,13 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
     end
 
     test "rejects token exchange for user with confirmation_pending set to true" do
     end
 
     test "rejects token exchange for user with confirmation_pending set to true" do
-      Pleroma.Config.put([:instance, :account_activation_required], true)
+      clear_config([:instance, :account_activation_required], true)
       password = "testpassword"
 
       user =
         insert(:user,
       password = "testpassword"
 
       user =
         insert(:user,
-          password_hash: Pbkdf2.hash_pwd_salt(password),
-          confirmation_pending: true
+          password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password),
+          is_confirmed: false
         )
 
       app = insert(:oauth_app, scopes: ["read", "write"])
         )
 
       app = insert(:oauth_app, scopes: ["read", "write"])
@@ -1001,7 +1108,11 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
     test "rejects token exchange for valid credentials belonging to an unapproved user" do
       password = "testpassword"
 
     test "rejects token exchange for valid credentials belonging to an unapproved user" do
       password = "testpassword"
 
-      user = insert(:user, password_hash: Pbkdf2.hash_pwd_salt(password), approval_pending: true)
+      user =
+        insert(:user,
+          password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password),
+          is_approved: false
+        )
 
       refute Pleroma.User.account_status(user) == :active
 
 
       refute Pleroma.User.account_status(user) == :active
 
@@ -1045,7 +1156,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
     setup do: clear_config([:oauth2, :issue_new_refresh_token])
 
     test "issues a new access token with keep fresh token" do
     setup do: clear_config([:oauth2, :issue_new_refresh_token])
 
     test "issues a new access token with keep fresh token" do
-      Pleroma.Config.put([:oauth2, :issue_new_refresh_token], true)
+      clear_config([:oauth2, :issue_new_refresh_token], true)
       user = insert(:user)
       app = insert(:oauth_app, scopes: ["read", "write"])
 
       user = insert(:user)
       app = insert(:oauth_app, scopes: ["read", "write"])
 
@@ -1068,7 +1179,6 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
                %{
                  "scope" => "write",
                  "token_type" => "Bearer",
                %{
                  "scope" => "write",
                  "token_type" => "Bearer",
-                 "expires_in" => 600,
                  "access_token" => _,
                  "refresh_token" => _,
                  "me" => ^ap_id
                  "access_token" => _,
                  "refresh_token" => _,
                  "me" => ^ap_id
@@ -1085,7 +1195,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
     end
 
     test "issues a new access token with new fresh token" do
     end
 
     test "issues a new access token with new fresh token" do
-      Pleroma.Config.put([:oauth2, :issue_new_refresh_token], false)
+      clear_config([:oauth2, :issue_new_refresh_token], false)
       user = insert(:user)
       app = insert(:oauth_app, scopes: ["read", "write"])
 
       user = insert(:user)
       app = insert(:oauth_app, scopes: ["read", "write"])
 
@@ -1108,7 +1218,6 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
                %{
                  "scope" => "write",
                  "token_type" => "Bearer",
                %{
                  "scope" => "write",
                  "token_type" => "Bearer",
-                 "expires_in" => 600,
                  "access_token" => _,
                  "refresh_token" => _,
                  "me" => ^ap_id
                  "access_token" => _,
                  "refresh_token" => _,
                  "me" => ^ap_id
@@ -1177,6 +1286,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       response =
         build_conn()
 
       response =
         build_conn()
+        |> put_req_header("authorization", "Bearer #{access_token.token}")
         |> post("/oauth/token", %{
           "grant_type" => "refresh_token",
           "refresh_token" => access_token.refresh_token,
         |> post("/oauth/token", %{
           "grant_type" => "refresh_token",
           "refresh_token" => access_token.refresh_token,
@@ -1191,7 +1301,6 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
                %{
                  "scope" => "write",
                  "token_type" => "Bearer",
                %{
                  "scope" => "write",
                  "token_type" => "Bearer",
-                 "expires_in" => 600,
                  "access_token" => _,
                  "refresh_token" => _,
                  "me" => ^ap_id
                  "access_token" => _,
                  "refresh_token" => _,
                  "me" => ^ap_id
@@ -1219,8 +1328,41 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
     end
   end
 
     end
   end
 
-  describe "POST /oauth/revoke - bad request" do
-    test "returns 500" do
+  describe "POST /oauth/revoke" do
+    test "when authenticated with request token, revokes it and clears it from session" do
+      oauth_token = insert(:oauth_token)
+
+      conn =
+        build_conn()
+        |> Plug.Session.call(Plug.Session.init(@session_opts))
+        |> fetch_session()
+        |> put_req_header("authorization", "Bearer #{oauth_token.token}")
+        |> post("/oauth/revoke", %{"token" => oauth_token.token})
+
+      assert json_response(conn, 200)
+
+      assert Token.get_by_token(oauth_token.token) == {:error, :not_found}
+    end
+
+    test "if request is authenticated with a different token, " <>
+           "revokes requested token but keeps session token" do
+      user = insert(:user)
+      oauth_token = insert(:oauth_token, user: user)
+      other_app_oauth_token = insert(:oauth_token, user: user)
+
+      conn =
+        build_conn()
+        |> Plug.Session.call(Plug.Session.init(@session_opts))
+        |> fetch_session()
+        |> put_req_header("authorization", "Bearer #{oauth_token.token}")
+        |> post("/oauth/revoke", %{"token" => other_app_oauth_token.token})
+
+      assert json_response(conn, 200)
+
+      assert Token.get_by_token(other_app_oauth_token.token) == {:error, :not_found}
+    end
+
+    test "returns 500 on bad request" do
       response =
         build_conn()
         |> post("/oauth/revoke", %{})
       response =
         build_conn()
         |> post("/oauth/revoke", %{})