Merge branch 'reactions' into 'develop'
[akkoma] / test / web / oauth / oauth_controller_test.exs
index 6e96537ecc9759834aa5dc10287ad71902303c55..beb995cd8435f5050eb77023c9fa906baa36d947 100644 (file)
@@ -1,15 +1,15 @@
 # Pleroma: A lightweight social networking server
 # Pleroma: A lightweight social networking server
-# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.OAuth.OAuthControllerTest do
   use Pleroma.Web.ConnCase
   import Pleroma.Factory
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.OAuth.OAuthControllerTest do
   use Pleroma.Web.ConnCase
   import Pleroma.Factory
-  import Mock
 
 
-  alias Pleroma.Registration
   alias Pleroma.Repo
   alias Pleroma.Repo
+  alias Pleroma.User
   alias Pleroma.Web.OAuth.Authorization
   alias Pleroma.Web.OAuth.Authorization
+  alias Pleroma.Web.OAuth.OAuthController
   alias Pleroma.Web.OAuth.Token
 
   @session_opts [
   alias Pleroma.Web.OAuth.Token
 
   @session_opts [
@@ -17,17 +17,10 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
     key: "_test",
     signing_salt: "cooldude"
   ]
     key: "_test",
     signing_salt: "cooldude"
   ]
+  clear_config_all([:instance, :account_activation_required])
 
   describe "in OAuth consumer mode, " do
     setup do
 
   describe "in OAuth consumer mode, " do
     setup do
-      oauth_consumer_strategies_path = [:auth, :oauth_consumer_strategies]
-      oauth_consumer_strategies = Pleroma.Config.get(oauth_consumer_strategies_path)
-      Pleroma.Config.put(oauth_consumer_strategies_path, ~w(twitter facebook))
-
-      on_exit(fn ->
-        Pleroma.Config.put(oauth_consumer_strategies_path, oauth_consumer_strategies)
-      end)
-
       [
         app: insert(:oauth_app),
         conn:
       [
         app: insert(:oauth_app),
         conn:
@@ -37,6 +30,13 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
       ]
     end
 
       ]
     end
 
+    clear_config([:auth, :oauth_consumer_strategies]) do
+      Pleroma.Config.put(
+        [:auth, :oauth_consumer_strategies],
+        ~w(twitter facebook)
+      )
+    end
+
     test "GET /oauth/authorize renders auth forms, including OAuth consumer form", %{
       app: app,
       conn: conn
     test "GET /oauth/authorize renders auth forms, including OAuth consumer form", %{
       app: app,
       conn: conn
@@ -48,7 +48,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
           %{
             "response_type" => "code",
             "client_id" => app.client_id,
           %{
             "response_type" => "code",
             "client_id" => app.client_id,
-            "redirect_uri" => app.redirect_uris,
+            "redirect_uri" => OAuthController.default_redirect_uri(app),
             "scope" => "read"
           }
         )
             "scope" => "read"
           }
         )
@@ -71,7 +71,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
             "authorization" => %{
               "scope" => "read follow",
               "client_id" => app.client_id,
             "authorization" => %{
               "scope" => "read follow",
               "client_id" => app.client_id,
-              "redirect_uri" => app.redirect_uris,
+              "redirect_uri" => OAuthController.default_redirect_uri(app),
               "state" => "a_state"
             }
           }
               "state" => "a_state"
             }
           }
@@ -97,64 +97,65 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
     test "with user-bound registration, GET /oauth/<provider>/callback redirects to `redirect_uri` with `code`",
          %{app: app, conn: conn} do
       registration = insert(:registration)
     test "with user-bound registration, GET /oauth/<provider>/callback redirects to `redirect_uri` with `code`",
          %{app: app, conn: conn} do
       registration = insert(:registration)
+      redirect_uri = OAuthController.default_redirect_uri(app)
 
       state_params = %{
         "scope" => Enum.join(app.scopes, " "),
         "client_id" => app.client_id,
 
       state_params = %{
         "scope" => Enum.join(app.scopes, " "),
         "client_id" => app.client_id,
-        "redirect_uri" => app.redirect_uris,
+        "redirect_uri" => redirect_uri,
         "state" => ""
       }
 
         "state" => ""
       }
 
-      with_mock Pleroma.Web.Auth.Authenticator,
-        get_registration: fn _ -> {:ok, registration} end do
-        conn =
-          get(
-            conn,
-            "/oauth/twitter/callback",
-            %{
-              "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
-              "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
-              "provider" => "twitter",
-              "state" => Poison.encode!(state_params)
-            }
-          )
+      conn =
+        conn
+        |> assign(:ueberauth_auth, %{provider: registration.provider, uid: registration.uid})
+        |> get(
+          "/oauth/twitter/callback",
+          %{
+            "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
+            "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
+            "provider" => "twitter",
+            "state" => Poison.encode!(state_params)
+          }
+        )
 
 
-        assert response = html_response(conn, 302)
-        assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
-      end
+      assert response = html_response(conn, 302)
+      assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
     end
 
     test "with user-unbound registration, GET /oauth/<provider>/callback renders registration_details page",
          %{app: app, conn: conn} do
     end
 
     test "with user-unbound registration, GET /oauth/<provider>/callback renders registration_details page",
          %{app: app, conn: conn} do
-      registration = insert(:registration, user: nil)
+      user = insert(:user)
 
       state_params = %{
         "scope" => "read write",
         "client_id" => app.client_id,
 
       state_params = %{
         "scope" => "read write",
         "client_id" => app.client_id,
-        "redirect_uri" => app.redirect_uris,
+        "redirect_uri" => OAuthController.default_redirect_uri(app),
         "state" => "a_state"
       }
 
         "state" => "a_state"
       }
 
-      with_mock Pleroma.Web.Auth.Authenticator,
-        get_registration: fn _ -> {:ok, registration} end do
-        conn =
-          get(
-            conn,
-            "/oauth/twitter/callback",
-            %{
-              "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
-              "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
-              "provider" => "twitter",
-              "state" => Poison.encode!(state_params)
-            }
-          )
+      conn =
+        conn
+        |> assign(:ueberauth_auth, %{
+          provider: "twitter",
+          uid: "171799000",
+          info: %{nickname: user.nickname, email: user.email, name: user.name, description: nil}
+        })
+        |> get(
+          "/oauth/twitter/callback",
+          %{
+            "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
+            "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
+            "provider" => "twitter",
+            "state" => Poison.encode!(state_params)
+          }
+        )
 
 
-        assert response = html_response(conn, 200)
-        assert response =~ ~r/name="op" type="submit" value="register"/
-        assert response =~ ~r/name="op" type="submit" value="connect"/
-        assert response =~ Registration.email(registration)
-        assert response =~ Registration.nickname(registration)
-      end
+      assert response = html_response(conn, 200)
+      assert response =~ ~r/name="op" type="submit" value="register"/
+      assert response =~ ~r/name="op" type="submit" value="connect"/
+      assert response =~ user.email
+      assert response =~ user.nickname
     end
 
     test "on authentication error, GET /oauth/<provider>/callback redirects to `redirect_uri`", %{
     end
 
     test "on authentication error, GET /oauth/<provider>/callback redirects to `redirect_uri`", %{
@@ -164,7 +165,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
       state_params = %{
         "scope" => Enum.join(app.scopes, " "),
         "client_id" => app.client_id,
       state_params = %{
         "scope" => Enum.join(app.scopes, " "),
         "client_id" => app.client_id,
-        "redirect_uri" => app.redirect_uris,
+        "redirect_uri" => OAuthController.default_redirect_uri(app),
         "state" => ""
       }
 
         "state" => ""
       }
 
@@ -198,7 +199,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
             "authorization" => %{
               "scopes" => app.scopes,
               "client_id" => app.client_id,
             "authorization" => %{
               "scopes" => app.scopes,
               "client_id" => app.client_id,
-              "redirect_uri" => app.redirect_uris,
+              "redirect_uri" => OAuthController.default_redirect_uri(app),
               "state" => "a_state",
               "nickname" => nil,
               "email" => "john@doe.com"
               "state" => "a_state",
               "nickname" => nil,
               "email" => "john@doe.com"
@@ -217,6 +218,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
            conn: conn
          } do
       registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
            conn: conn
          } do
       registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
+      redirect_uri = OAuthController.default_redirect_uri(app)
 
       conn =
         conn
 
       conn =
         conn
@@ -228,7 +230,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
             "authorization" => %{
               "scopes" => app.scopes,
               "client_id" => app.client_id,
             "authorization" => %{
               "scopes" => app.scopes,
               "client_id" => app.client_id,
-              "redirect_uri" => app.redirect_uris,
+              "redirect_uri" => redirect_uri,
               "state" => "a_state",
               "nickname" => "availablenick",
               "email" => "available@email.com"
               "state" => "a_state",
               "nickname" => "availablenick",
               "email" => "available@email.com"
@@ -237,7 +239,36 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
         )
 
       assert response = html_response(conn, 302)
         )
 
       assert response = html_response(conn, 302)
-      assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
+      assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
+    end
+
+    test "with unlisted `redirect_uri`, POST /oauth/register?op=register results in HTTP 401",
+         %{
+           app: app,
+           conn: conn
+         } do
+      registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
+      unlisted_redirect_uri = "http://cross-site-request.com"
+
+      conn =
+        conn
+        |> put_session(:registration_id, registration.id)
+        |> post(
+          "/oauth/register",
+          %{
+            "op" => "register",
+            "authorization" => %{
+              "scopes" => app.scopes,
+              "client_id" => app.client_id,
+              "redirect_uri" => unlisted_redirect_uri,
+              "state" => "a_state",
+              "nickname" => "availablenick",
+              "email" => "available@email.com"
+            }
+          }
+        )
+
+      assert response = 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",
@@ -253,7 +284,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
         "authorization" => %{
           "scopes" => app.scopes,
           "client_id" => app.client_id,
         "authorization" => %{
           "scopes" => app.scopes,
           "client_id" => app.client_id,
-          "redirect_uri" => app.redirect_uris,
+          "redirect_uri" => OAuthController.default_redirect_uri(app),
           "state" => "a_state",
           "nickname" => "availablenickname",
           "email" => "available@email.com"
           "state" => "a_state",
           "nickname" => "availablenickname",
           "email" => "available@email.com"
@@ -285,6 +316,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
          } do
       user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("testpassword"))
       registration = insert(:registration, user: nil)
          } do
       user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("testpassword"))
       registration = insert(:registration, user: nil)
+      redirect_uri = OAuthController.default_redirect_uri(app)
 
       conn =
         conn
 
       conn =
         conn
@@ -296,7 +328,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
             "authorization" => %{
               "scopes" => app.scopes,
               "client_id" => app.client_id,
             "authorization" => %{
               "scopes" => app.scopes,
               "client_id" => app.client_id,
-              "redirect_uri" => app.redirect_uris,
+              "redirect_uri" => redirect_uri,
               "state" => "a_state",
               "name" => user.nickname,
               "password" => "testpassword"
               "state" => "a_state",
               "name" => user.nickname,
               "password" => "testpassword"
@@ -305,7 +337,37 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
         )
 
       assert response = html_response(conn, 302)
         )
 
       assert response = html_response(conn, 302)
-      assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
+      assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
+    end
+
+    test "with unlisted `redirect_uri`, POST /oauth/register?op=connect results in HTTP 401`",
+         %{
+           app: app,
+           conn: conn
+         } do
+      user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("testpassword"))
+      registration = insert(:registration, user: nil)
+      unlisted_redirect_uri = "http://cross-site-request.com"
+
+      conn =
+        conn
+        |> put_session(:registration_id, registration.id)
+        |> post(
+          "/oauth/register",
+          %{
+            "op" => "connect",
+            "authorization" => %{
+              "scopes" => app.scopes,
+              "client_id" => app.client_id,
+              "redirect_uri" => unlisted_redirect_uri,
+              "state" => "a_state",
+              "name" => user.nickname,
+              "password" => "testpassword"
+            }
+          }
+        )
+
+      assert response = 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",
@@ -321,7 +383,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
         "authorization" => %{
           "scopes" => app.scopes,
           "client_id" => app.client_id,
         "authorization" => %{
           "scopes" => app.scopes,
           "client_id" => app.client_id,
-          "redirect_uri" => app.redirect_uris,
+          "redirect_uri" => OAuthController.default_redirect_uri(app),
           "state" => "a_state",
           "name" => user.nickname,
           "password" => "wrong password"
           "state" => "a_state",
           "name" => user.nickname,
           "password" => "wrong password"
@@ -357,7 +419,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
           %{
             "response_type" => "code",
             "client_id" => app.client_id,
           %{
             "response_type" => "code",
             "client_id" => app.client_id,
-            "redirect_uri" => app.redirect_uris,
+            "redirect_uri" => OAuthController.default_redirect_uri(app),
             "scope" => "read"
           }
         )
             "scope" => "read"
           }
         )
@@ -377,7 +439,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
             "authorization" => %{
               "response_type" => "code",
               "client_id" => app.client_id,
             "authorization" => %{
               "response_type" => "code",
               "client_id" => app.client_id,
-              "redirect_uri" => app.redirect_uris,
+              "redirect_uri" => OAuthController.default_redirect_uri(app),
               "scope" => "read"
             }
           }
               "scope" => "read"
             }
           }
@@ -398,7 +460,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
           %{
             "response_type" => "code",
             "client_id" => app.client_id,
           %{
             "response_type" => "code",
             "client_id" => app.client_id,
-            "redirect_uri" => app.redirect_uris,
+            "redirect_uri" => OAuthController.default_redirect_uri(app),
             "scope" => "read",
             "force_login" => "true"
           }
             "scope" => "read",
             "force_login" => "true"
           }
@@ -407,7 +469,60 @@ 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 "redirects to app if user is already authenticated", %{app: app, conn: conn} do
+    test "renders authentication page if user is already authenticated but user request with another client",
+         %{
+           app: app,
+           conn: conn
+         } do
+      token = insert(:oauth_token, app_id: app.id)
+
+      conn =
+        conn
+        |> put_session(:oauth_token, token.token)
+        |> get(
+          "/oauth/authorize",
+          %{
+            "response_type" => "code",
+            "client_id" => "another_client_id",
+            "redirect_uri" => OAuthController.default_redirect_uri(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,
+           conn: conn
+         } do
+      token = insert(:oauth_token, app_id: app.id)
+
+      conn =
+        conn
+        |> put_session(:oauth_token, token.token)
+        |> get(
+          "/oauth/authorize",
+          %{
+            "response_type" => "code",
+            "client_id" => app.client_id,
+            "redirect_uri" => OAuthController.default_redirect_uri(app),
+            "state" => "specific_client_state",
+            "scope" => "read"
+          }
+        )
+
+      assert URI.decode(redirected_to(conn)) ==
+               "https://redirect.url?access_token=#{token.token}&state=specific_client_state"
+    end
+
+    test "with existing authentication and unlisted non-OOB `redirect_uri`, redirects without credentials",
+         %{
+           app: app,
+           conn: conn
+         } do
+      unlisted_redirect_uri = "http://cross-site-request.com"
       token = insert(:oauth_token, app_id: app.id)
 
       conn =
       token = insert(:oauth_token, app_id: app.id)
 
       conn =
@@ -418,12 +533,36 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
           %{
             "response_type" => "code",
             "client_id" => app.client_id,
           %{
             "response_type" => "code",
             "client_id" => app.client_id,
-            "redirect_uri" => app.redirect_uris,
+            "redirect_uri" => unlisted_redirect_uri,
+            "state" => "specific_client_state",
             "scope" => "read"
           }
         )
 
             "scope" => "read"
           }
         )
 
-      assert redirected_to(conn) == "https://redirect.url"
+      assert redirected_to(conn) == unlisted_redirect_uri
+    end
+
+    test "with existing authentication and OOB `redirect_uri`, redirects to app with `token` and `state` params",
+         %{
+           app: app,
+           conn: conn
+         } do
+      token = insert(:oauth_token, app_id: app.id)
+
+      conn =
+        conn
+        |> put_session(:oauth_token, token.token)
+        |> get(
+          "/oauth/authorize",
+          %{
+            "response_type" => "code",
+            "client_id" => app.client_id,
+            "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
+            "scope" => "read"
+          }
+        )
+
+      assert html_response(conn, 200) =~ "Authorization exists"
     end
   end
 
     end
   end
 
@@ -431,6 +570,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
     test "redirects with oauth authorization" do
       user = insert(:user)
       app = insert(:oauth_app, scopes: ["read", "write", "follow"])
     test "redirects with oauth authorization" do
       user = insert(:user)
       app = insert(:oauth_app, scopes: ["read", "write", "follow"])
+      redirect_uri = OAuthController.default_redirect_uri(app)
 
       conn =
         build_conn()
 
       conn =
         build_conn()
@@ -439,26 +579,27 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
             "name" => user.nickname,
             "password" => "test",
             "client_id" => app.client_id,
             "name" => user.nickname,
             "password" => "test",
             "client_id" => app.client_id,
-            "redirect_uri" => app.redirect_uris,
-            "scope" => "read write",
+            "redirect_uri" => redirect_uri,
+            "scope" => "read:subscope write",
             "state" => "statepassed"
           }
         })
 
       target = redirected_to(conn)
             "state" => "statepassed"
           }
         })
 
       target = redirected_to(conn)
-      assert target =~ app.redirect_uris
+      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
 
       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 == ["read", "write"]
+      assert auth.scopes == ["read:subscope", "write"]
     end
 
     test "returns 401 for wrong credentials", %{conn: conn} do
       user = insert(:user)
       app = insert(:oauth_app)
     end
 
     test "returns 401 for wrong credentials", %{conn: conn} do
       user = insert(:user)
       app = insert(:oauth_app)
+      redirect_uri = OAuthController.default_redirect_uri(app)
 
       result =
         conn
 
       result =
         conn
@@ -467,7 +608,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
             "name" => user.nickname,
             "password" => "wrong",
             "client_id" => app.client_id,
             "name" => user.nickname,
             "password" => "wrong",
             "client_id" => app.client_id,
-            "redirect_uri" => app.redirect_uris,
+            "redirect_uri" => redirect_uri,
             "state" => "statepassed",
             "scope" => Enum.join(app.scopes, " ")
           }
             "state" => "statepassed",
             "scope" => Enum.join(app.scopes, " ")
           }
@@ -476,7 +617,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       # Keep the details
       assert result =~ app.client_id
 
       # Keep the details
       assert result =~ app.client_id
-      assert result =~ app.redirect_uris
+      assert result =~ redirect_uri
 
       # Error message
       assert result =~ "Invalid Username/Password"
 
       # Error message
       assert result =~ "Invalid Username/Password"
@@ -485,6 +626,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
     test "returns 401 for missing scopes", %{conn: conn} do
       user = insert(:user)
       app = insert(:oauth_app)
     test "returns 401 for missing scopes", %{conn: conn} do
       user = insert(:user)
       app = insert(:oauth_app)
+      redirect_uri = OAuthController.default_redirect_uri(app)
 
       result =
         conn
 
       result =
         conn
@@ -493,7 +635,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
             "name" => user.nickname,
             "password" => "test",
             "client_id" => app.client_id,
             "name" => user.nickname,
             "password" => "test",
             "client_id" => app.client_id,
-            "redirect_uri" => app.redirect_uris,
+            "redirect_uri" => redirect_uri,
             "state" => "statepassed",
             "scope" => ""
           }
             "state" => "statepassed",
             "scope" => ""
           }
@@ -502,15 +644,16 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       # Keep the details
       assert result =~ app.client_id
 
       # Keep the details
       assert result =~ app.client_id
-      assert result =~ app.redirect_uris
+      assert result =~ redirect_uri
 
       # Error message
       assert result =~ "This action is outside the authorized scopes"
     end
 
 
       # Error message
       assert result =~ "This action is outside the authorized scopes"
     end
 
-    test "returns 401 for scopes beyond app scopes", %{conn: conn} do
+    test "returns 401 for scopes beyond app scopes hierarchy", %{conn: conn} do
       user = insert(:user)
       app = insert(:oauth_app, scopes: ["read", "write"])
       user = insert(:user)
       app = insert(:oauth_app, scopes: ["read", "write"])
+      redirect_uri = OAuthController.default_redirect_uri(app)
 
       result =
         conn
 
       result =
         conn
@@ -519,7 +662,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
             "name" => user.nickname,
             "password" => "test",
             "client_id" => app.client_id,
             "name" => user.nickname,
             "password" => "test",
             "client_id" => app.client_id,
-            "redirect_uri" => app.redirect_uris,
+            "redirect_uri" => redirect_uri,
             "state" => "statepassed",
             "scope" => "read write follow"
           }
             "state" => "statepassed",
             "scope" => "read write follow"
           }
@@ -528,7 +671,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       # Keep the details
       assert result =~ app.client_id
 
       # Keep the details
       assert result =~ app.client_id
-      assert result =~ app.redirect_uris
+      assert result =~ redirect_uri
 
       # Error message
       assert result =~ "This action is outside the authorized scopes"
 
       # Error message
       assert result =~ "This action is outside the authorized scopes"
@@ -547,7 +690,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
         |> post("/oauth/token", %{
           "grant_type" => "authorization_code",
           "code" => auth.token,
         |> post("/oauth/token", %{
           "grant_type" => "authorization_code",
           "code" => auth.token,
-          "redirect_uri" => app.redirect_uris,
+          "redirect_uri" => OAuthController.default_redirect_uri(app),
           "client_id" => app.client_id,
           "client_secret" => app.client_secret
         })
           "client_id" => app.client_id,
           "client_secret" => app.client_secret
         })
@@ -601,7 +744,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
         |> post("/oauth/token", %{
           "grant_type" => "authorization_code",
           "code" => auth.token,
         |> post("/oauth/token", %{
           "grant_type" => "authorization_code",
           "code" => auth.token,
-          "redirect_uri" => app.redirect_uris
+          "redirect_uri" => OAuthController.default_redirect_uri(app)
         })
 
       assert %{"access_token" => token, "scope" => scope} = json_response(conn, 200)
         })
 
       assert %{"access_token" => token, "scope" => scope} = json_response(conn, 200)
@@ -613,6 +756,27 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
       assert token.scopes == ["scope1", "scope2"]
     end
 
       assert token.scopes == ["scope1", "scope2"]
     end
 
+    test "issue a token for client_credentials grant type" do
+      app = insert(:oauth_app, scopes: ["read", "write"])
+
+      conn =
+        build_conn()
+        |> post("/oauth/token", %{
+          "grant_type" => "client_credentials",
+          "client_id" => app.client_id,
+          "client_secret" => app.client_secret
+        })
+
+      assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
+               json_response(conn, 200)
+
+      assert token
+      token_from_db = Repo.get_by(Token, token: token)
+      assert token_from_db
+      assert refresh
+      assert scope == "read write"
+    end
+
     test "rejects token exchange with invalid client credentials" do
       user = insert(:user)
       app = insert(:oauth_app)
     test "rejects token exchange with invalid client credentials" do
       user = insert(:user)
       app = insert(:oauth_app)
@@ -625,7 +789,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
         |> post("/oauth/token", %{
           "grant_type" => "authorization_code",
           "code" => auth.token,
         |> post("/oauth/token", %{
           "grant_type" => "authorization_code",
           "code" => auth.token,
-          "redirect_uri" => app.redirect_uris
+          "redirect_uri" => OAuthController.default_redirect_uri(app)
         })
 
       assert resp = json_response(conn, 400)
         })
 
       assert resp = json_response(conn, 400)
@@ -634,22 +798,13 @@ 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
-      setting = Pleroma.Config.get([:instance, :account_activation_required])
-
-      unless setting do
-        Pleroma.Config.put([:instance, :account_activation_required], true)
-        on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end)
-      end
-
+      Pleroma.Config.put([:instance, :account_activation_required], true)
       password = "testpassword"
       password = "testpassword"
-      user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
-      info_change = Pleroma.User.Info.confirmation_changeset(user.info, :unconfirmed)
 
       {:ok, user} =
 
       {:ok, user} =
-        user
-        |> Ecto.Changeset.change()
-        |> Ecto.Changeset.put_embed(:info, info_change)
-        |> Repo.update()
+        insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
+        |> User.confirmation_changeset(need_confirmation: true)
+        |> User.update_and_set_cache()
 
       refute Pleroma.User.auth_active?(user)
 
 
       refute Pleroma.User.auth_active?(user)
 
@@ -676,7 +831,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
       user =
         insert(:user,
           password_hash: Comeonin.Pbkdf2.hashpwsalt(password),
       user =
         insert(:user,
           password_hash: Comeonin.Pbkdf2.hashpwsalt(password),
-          info: %{deactivated: true}
+          deactivated: true
         )
 
       app = insert(:oauth_app)
         )
 
       app = insert(:oauth_app)
@@ -696,6 +851,34 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
       refute Map.has_key?(resp, "access_token")
     end
 
       refute Map.has_key?(resp, "access_token")
     end
 
+    test "rejects token exchange for user with password_reset_pending set to true" do
+      password = "testpassword"
+
+      user =
+        insert(:user,
+          password_hash: Comeonin.Pbkdf2.hashpwsalt(password),
+          password_reset_pending: true
+        )
+
+      app = insert(:oauth_app, scopes: ["read", "write"])
+
+      conn =
+        build_conn()
+        |> post("/oauth/token", %{
+          "grant_type" => "password",
+          "username" => user.nickname,
+          "password" => password,
+          "client_id" => app.client_id,
+          "client_secret" => app.client_secret
+        })
+
+      assert resp = json_response(conn, 403)
+
+      assert resp["error"] == "Password reset is required"
+      assert resp["identifier"] == "password_reset_required"
+      refute Map.has_key?(resp, "access_token")
+    end
+
     test "rejects an invalid authorization code" do
       app = insert(:oauth_app)
 
     test "rejects an invalid authorization code" do
       app = insert(:oauth_app)
 
@@ -704,7 +887,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
         |> post("/oauth/token", %{
           "grant_type" => "authorization_code",
           "code" => "Imobviouslyinvalid",
         |> post("/oauth/token", %{
           "grant_type" => "authorization_code",
           "code" => "Imobviouslyinvalid",
-          "redirect_uri" => app.redirect_uris,
+          "redirect_uri" => OAuthController.default_redirect_uri(app),
           "client_id" => app.client_id,
           "client_secret" => app.client_secret
         })
           "client_id" => app.client_id,
           "client_secret" => app.client_secret
         })
@@ -714,4 +897,193 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
       refute Map.has_key?(resp, "access_token")
     end
   end
       refute Map.has_key?(resp, "access_token")
     end
   end
+
+  describe "POST /oauth/token - refresh token" 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)
+      user = insert(:user)
+      app = insert(:oauth_app, scopes: ["read", "write"])
+
+      {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
+      {:ok, token} = Token.exchange_token(app, auth)
+
+      response =
+        build_conn()
+        |> post("/oauth/token", %{
+          "grant_type" => "refresh_token",
+          "refresh_token" => token.refresh_token,
+          "client_id" => app.client_id,
+          "client_secret" => app.client_secret
+        })
+        |> json_response(200)
+
+      ap_id = user.ap_id
+
+      assert match?(
+               %{
+                 "scope" => "write",
+                 "token_type" => "Bearer",
+                 "expires_in" => 600,
+                 "access_token" => _,
+                 "refresh_token" => _,
+                 "me" => ^ap_id
+               },
+               response
+             )
+
+      refute Repo.get_by(Token, token: token.token)
+      new_token = Repo.get_by(Token, token: response["access_token"])
+      assert new_token.refresh_token == token.refresh_token
+      assert new_token.scopes == auth.scopes
+      assert new_token.user_id == user.id
+      assert new_token.app_id == app.id
+    end
+
+    test "issues a new access token with new fresh token" do
+      Pleroma.Config.put([:oauth2, :issue_new_refresh_token], false)
+      user = insert(:user)
+      app = insert(:oauth_app, scopes: ["read", "write"])
+
+      {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
+      {:ok, token} = Token.exchange_token(app, auth)
+
+      response =
+        build_conn()
+        |> post("/oauth/token", %{
+          "grant_type" => "refresh_token",
+          "refresh_token" => token.refresh_token,
+          "client_id" => app.client_id,
+          "client_secret" => app.client_secret
+        })
+        |> json_response(200)
+
+      ap_id = user.ap_id
+
+      assert match?(
+               %{
+                 "scope" => "write",
+                 "token_type" => "Bearer",
+                 "expires_in" => 600,
+                 "access_token" => _,
+                 "refresh_token" => _,
+                 "me" => ^ap_id
+               },
+               response
+             )
+
+      refute Repo.get_by(Token, token: token.token)
+      new_token = Repo.get_by(Token, token: response["access_token"])
+      refute new_token.refresh_token == token.refresh_token
+      assert new_token.scopes == auth.scopes
+      assert new_token.user_id == user.id
+      assert new_token.app_id == app.id
+    end
+
+    test "returns 400 if we try use access token" do
+      user = insert(:user)
+      app = insert(:oauth_app, scopes: ["read", "write"])
+
+      {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
+      {:ok, token} = Token.exchange_token(app, auth)
+
+      response =
+        build_conn()
+        |> post("/oauth/token", %{
+          "grant_type" => "refresh_token",
+          "refresh_token" => token.token,
+          "client_id" => app.client_id,
+          "client_secret" => app.client_secret
+        })
+        |> json_response(400)
+
+      assert %{"error" => "Invalid credentials"} == response
+    end
+
+    test "returns 400 if refresh_token invalid" do
+      app = insert(:oauth_app, scopes: ["read", "write"])
+
+      response =
+        build_conn()
+        |> post("/oauth/token", %{
+          "grant_type" => "refresh_token",
+          "refresh_token" => "token.refresh_token",
+          "client_id" => app.client_id,
+          "client_secret" => app.client_secret
+        })
+        |> json_response(400)
+
+      assert %{"error" => "Invalid credentials"} == response
+    end
+
+    test "issues a new token if token expired" do
+      user = insert(:user)
+      app = insert(:oauth_app, scopes: ["read", "write"])
+
+      {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
+      {:ok, token} = Token.exchange_token(app, auth)
+
+      change =
+        Ecto.Changeset.change(
+          token,
+          %{valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), -86_400 * 30)}
+        )
+
+      {:ok, access_token} = Repo.update(change)
+
+      response =
+        build_conn()
+        |> post("/oauth/token", %{
+          "grant_type" => "refresh_token",
+          "refresh_token" => access_token.refresh_token,
+          "client_id" => app.client_id,
+          "client_secret" => app.client_secret
+        })
+        |> json_response(200)
+
+      ap_id = user.ap_id
+
+      assert match?(
+               %{
+                 "scope" => "write",
+                 "token_type" => "Bearer",
+                 "expires_in" => 600,
+                 "access_token" => _,
+                 "refresh_token" => _,
+                 "me" => ^ap_id
+               },
+               response
+             )
+
+      refute Repo.get_by(Token, token: token.token)
+      token = Repo.get_by(Token, token: response["access_token"])
+      assert token
+      assert token.scopes == auth.scopes
+      assert token.user_id == user.id
+      assert token.app_id == app.id
+    end
+  end
+
+  describe "POST /oauth/token - bad request" do
+    test "returns 500" do
+      response =
+        build_conn()
+        |> post("/oauth/token", %{})
+        |> json_response(500)
+
+      assert %{"error" => "Bad request"} == response
+    end
+  end
+
+  describe "POST /oauth/revoke - bad request" do
+    test "returns 500" do
+      response =
+        build_conn()
+        |> post("/oauth/revoke", %{})
+        |> json_response(500)
+
+      assert %{"error" => "Bad request"} == response
+    end
+  end
 end
 end