do not allow non-admins to register tokens with admin scopes
[akkoma] / test / pleroma / web / o_auth / o_auth_controller_test.exs
index 1c5438cc297d5a7d28ce37b92392842490e49725..19a7dea60ef84e081806a804e7f4dd8f0d5d8c62 100644 (file)
@@ -316,7 +316,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
            app: app,
            conn: conn
          } do
            app: app,
            conn: conn
          } do
-      user = insert(:user, password_hash: Pleroma.Password.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)
 
@@ -347,7 +347,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
            app: app,
            conn: conn
          } do
            app: app,
            conn: conn
          } do
-      user = insert(:user, password_hash: Pleroma.Password.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"
 
@@ -494,6 +494,129 @@ 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 "allows access if the user has a prior authorization but is authenticated with another client",
+         %{
+           app: app,
+           conn: conn
+         } do
+      user = insert(:user)
+      token = insert(:oauth_token, app: app, user: user)
+
+      other_app = insert(:oauth_app, redirect_uris: "https://other_redirect.url")
+      authorization = insert(:oauth_authorization, user: user, app: other_app)
+      _reusable_token = insert(:oauth_token, app: other_app, user: user)
+
+      conn =
+        conn
+        |> AuthHelper.put_session_token(token.token)
+        |> AuthHelper.put_session_user(user.id)
+        |> get(
+          "/oauth/authorize",
+          %{
+            "response_type" => "code",
+            "client_id" => other_app.client_id,
+            "redirect_uri" => OAuthController.default_redirect_uri(other_app),
+            "scope" => "read"
+          }
+        )
+
+      assert URI.decode(redirected_to(conn)) ==
+               "https://other_redirect.url?code=#{authorization.token}"
+    end
+
+    test "renders login page if the user has an authorization but no token",
+         %{
+           app: app,
+           conn: conn
+         } do
+      user = insert(:user)
+      token = insert(:oauth_token, app: app, user: user)
+
+      other_app = insert(:oauth_app, redirect_uris: "https://other_redirect.url")
+      _authorization = insert(:oauth_authorization, user: user, app: other_app)
+
+      conn =
+        conn
+        |> AuthHelper.put_session_token(token.token)
+        |> AuthHelper.put_session_user(user.id)
+        |> 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 other people's tokens",
+         %{
+           app: app,
+           conn: conn
+         } do
+      user = insert(:user)
+      other_user = insert(:user)
+      token = insert(:oauth_token, app: app, user: user)
+
+      other_app = insert(:oauth_app, redirect_uris: "https://other_redirect.url")
+      _authorization = insert(:oauth_authorization, user: other_user, app: other_app)
+      _reusable_token = insert(:oauth_token, app: other_app, user: other_user)
+
+      conn =
+        conn
+        |> AuthHelper.put_session_token(token.token)
+        |> AuthHelper.put_session_user(user.id)
+        |> 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",
+         %{
+           app: app,
+           conn: conn
+         } do
+      user = insert(:user)
+      token = insert(:oauth_token, app: app, user: user)
+
+      other_app = insert(:oauth_app, redirect_uris: "https://other_redirect.url")
+      _authorization = insert(:oauth_authorization, user: user, app: other_app)
+
+      _reusable_token =
+        insert(:oauth_token,
+          app: other_app,
+          user: user,
+          valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), -100)
+        )
+
+      conn =
+        conn
+        |> AuthHelper.put_session_token(token.token)
+        |> AuthHelper.put_session_user(user.id)
+        |> 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 "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,
@@ -570,45 +693,76 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
   describe "POST /oauth/authorize" do
     test "redirects with oauth authorization, " <>
 
   describe "POST /oauth/authorize" do
     test "redirects with oauth authorization, " <>
-           "granting requested app-supported scopes to both admin- and non-admin users" do
+           "granting requested app-supported scopes to both admin users" do
       app_scopes = ["read", "write", "admin", "secret_scope"]
       app = insert(:oauth_app, scopes: app_scopes)
       redirect_uri = OAuthController.default_redirect_uri(app)
       app_scopes = ["read", "write", "admin", "secret_scope"]
       app = insert(:oauth_app, scopes: app_scopes)
       redirect_uri = OAuthController.default_redirect_uri(app)
+      scopes_subset = ["read:subscope", "write", "admin"]
+      admin = insert(:user, is_admin: true)
+
+      # In case scope param is missing, expecting _all_ app-supported scopes to be granted
+      conn =
+        post(
+          build_conn(),
+          "/oauth/authorize",
+          %{
+            "authorization" => %{
+              "name" => admin.nickname,
+              "password" => "test",
+              "client_id" => app.client_id,
+              "redirect_uri" => redirect_uri,
+              "scope" => scopes_subset,
+              "state" => "statepassed"
+            }
+          }
+        )
+
+      target = redirected_to(conn)
+      assert target =~ redirect_uri
+
+      query = URI.parse(target).query |> URI.query_decoder() |> Map.new()
+
+      assert %{"state" => "statepassed", "code" => code} = query
+      auth = Repo.get_by(Authorization, token: code)
+      assert auth
+      assert auth.scopes == scopes_subset
+    end
+
+    test "redirects with oauth authorization, " <>
+           "granting requested app-supported scopes for non-admin users" do
+      app_scopes = ["read", "write", "secret_scope", "admin"]
+      app = insert(:oauth_app, scopes: app_scopes)
+      redirect_uri = OAuthController.default_redirect_uri(app)
 
       non_admin = insert(:user, is_admin: false)
 
       non_admin = insert(:user, is_admin: false)
-      admin = insert(:user, is_admin: true)
-      scopes_subset = ["read:subscope", "write", "admin"]
+      scopes_subset = ["read:subscope", "write"]
 
       # In case scope param is missing, expecting _all_ app-supported scopes to be granted
 
       # In case scope param is missing, expecting _all_ app-supported scopes to be granted
-      for user <- [non_admin, admin],
-          {requested_scopes, expected_scopes} <-
-            %{scopes_subset => scopes_subset, nil: app_scopes} do
-        conn =
-          post(
-            build_conn(),
-            "/oauth/authorize",
-            %{
-              "authorization" => %{
-                "name" => user.nickname,
-                "password" => "test",
-                "client_id" => app.client_id,
-                "redirect_uri" => redirect_uri,
-                "scope" => requested_scopes,
-                "state" => "statepassed"
-              }
+      conn =
+        post(
+          build_conn(),
+          "/oauth/authorize",
+          %{
+            "authorization" => %{
+              "name" => non_admin.nickname,
+              "password" => "test",
+              "client_id" => app.client_id,
+              "redirect_uri" => redirect_uri,
+              "scope" => scopes_subset,
+              "state" => "statepassed"
             }
             }
-          )
+          }
+        )
 
 
-        target = redirected_to(conn)
-        assert target =~ redirect_uri
+      target = redirected_to(conn)
+      assert target =~ redirect_uri
 
 
-        query = URI.parse(target).query |> URI.query_decoder() |> Map.new()
+      query = URI.parse(target).query |> URI.query_decoder() |> Map.new()
 
 
-        assert %{"state" => "statepassed", "code" => code} = query
-        auth = Repo.get_by(Authorization, token: code)
-        assert auth
-        assert auth.scopes == expected_scopes
-      end
+      assert %{"state" => "statepassed", "code" => code} = query
+      auth = Repo.get_by(Authorization, token: code)
+      assert auth
+      assert auth.scopes == scopes_subset
     end
 
     test "authorize from cookie" do
     end
 
     test "authorize from cookie" do
@@ -616,6 +770,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
       app = insert(:oauth_app)
       oauth_token = insert(:oauth_token, user: user, app: app)
       redirect_uri = OAuthController.default_redirect_uri(app)
       app = insert(:oauth_app)
       oauth_token = insert(:oauth_token, user: user, app: app)
       redirect_uri = OAuthController.default_redirect_uri(app)
+      IO.inspect(app)
 
       conn =
         build_conn()
 
       conn =
         build_conn()
@@ -708,6 +863,33 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
       assert result =~ "Invalid Username/Password"
     end
 
       assert result =~ "Invalid Username/Password"
     end
 
+    test "returns 401 when attempting to use an admin scope with a non-admin", %{conn: conn} do
+      user = insert(:user)
+      app = insert(:oauth_app, scopes: ["admin"])
+      redirect_uri = OAuthController.default_redirect_uri(app)
+
+      result =
+        conn
+        |> post("/oauth/authorize", %{
+          "authorization" => %{
+            "name" => user.nickname,
+            "password" => "test",
+            "client_id" => app.client_id,
+            "redirect_uri" => redirect_uri,
+            "state" => "statepassed",
+            "scope" => Enum.join(app.scopes, " ")
+          }
+        })
+        |> html_response(:unauthorized)
+
+      # Keep the details
+      assert result =~ app.client_id
+      assert result =~ redirect_uri
+
+      # Error message
+      assert result =~ "outside of authorized scopes"
+    end
+
     test "returns 401 for missing scopes" do
       user = insert(:user, is_admin: false)
       app = insert(:oauth_app, scopes: ["read", "write", "admin"])
     test "returns 401 for missing scopes" do
       user = insert(:user, is_admin: false)
       app = insert(:oauth_app, scopes: ["read", "write", "admin"])
@@ -732,7 +914,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
       assert result =~ redirect_uri
 
       # Error message
       assert result =~ redirect_uri
 
       # Error message
-      assert result =~ "This action is outside the authorized scopes"
+      assert result =~ "This action is outside of authorized scopes"
     end
 
     test "returns 401 for scopes beyond app scopes hierarchy", %{conn: conn} do
     end
 
     test "returns 401 for scopes beyond app scopes hierarchy", %{conn: conn} do
@@ -759,7 +941,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
       assert result =~ redirect_uri
 
       # Error message
       assert result =~ redirect_uri
 
       # Error message
-      assert result =~ "This action is outside the authorized scopes"
+      assert result =~ "This action is outside of authorized scopes"
     end
   end
 
     end
   end
 
@@ -790,7 +972,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: Pleroma.Password.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"])
 
@@ -805,10 +987,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
 
@@ -818,7 +1002,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       user =
         insert(:user,
 
       user =
         insert(:user,
-          password_hash: Pleroma.Password.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}
@@ -923,12 +1107,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: Pleroma.Password.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
@@ -955,8 +1139,8 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       user =
         insert(:user,
 
       user =
         insert(:user,
-          password_hash: Pleroma.Password.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)
@@ -983,7 +1167,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       user =
         insert(:user,
 
       user =
         insert(:user,
-          password_hash: Pleroma.Password.hash_pwd_salt(password),
+          password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password),
           password_reset_pending: true
         )
 
           password_reset_pending: true
         )
 
@@ -1007,13 +1191,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: Pleroma.Password.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"])
@@ -1038,7 +1222,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: Pleroma.Password.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
 
@@ -1082,7 +1270,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"])
 
@@ -1121,7 +1309,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"])