From 04ee877a20a849db53a307a1736e635229129b7a Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Tue, 19 Feb 2019 22:28:21 +0300
Subject: [PATCH] [#468] Added OAuth scopes-specific tests.

---
 .../mastodon_api_controller_test.exs          | 18 ++++
 test/web/oauth/authorization_test.exs         | 50 ++++------
 test/web/oauth/oauth_controller_test.exs      | 96 ++++++++++++++++---
 test/web/oauth/token_test.exs                 |  8 +-
 .../twitter_api_controller_test.exs           | 18 ++++
 test/web/twitter_api/util_controller_test.exs | 19 ++++
 6 files changed, 162 insertions(+), 47 deletions(-)

diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs
index e43bc4508..8dcbde48b 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -1536,6 +1536,24 @@ test "updates the user's banner", %{conn: conn} do
       assert user_response = json_response(conn, 200)
       assert user_response["header"] != User.banner_url(user)
     end
+
+    test "requires 'write' permission", %{conn: conn} do
+      token1 = insert(:oauth_token, scopes: ["read"])
+      token2 = insert(:oauth_token, scopes: ["write", "follow"])
+
+      for token <- [token1, token2] do
+        conn =
+          conn
+          |> put_req_header("authorization", "Bearer #{token.token}")
+          |> patch("/api/v1/accounts/update_credentials", %{})
+
+        if token == token1 do
+          assert %{"error" => "Insufficient permissions: write."} == json_response(conn, 403)
+        else
+          assert json_response(conn, 200)
+        end
+      end
+    end
   end
 
   test "get instance information", %{conn: conn} do
diff --git a/test/web/oauth/authorization_test.exs b/test/web/oauth/authorization_test.exs
index b1a51e30e..306db2e62 100644
--- a/test/web/oauth/authorization_test.exs
+++ b/test/web/oauth/authorization_test.exs
@@ -8,36 +8,37 @@ defmodule Pleroma.Web.OAuth.AuthorizationTest do
   alias Pleroma.Web.OAuth.App
   import Pleroma.Factory
 
-  test "create an authorization token for a valid app" do
+  setup do
     {:ok, app} =
       Repo.insert(
         App.register_changeset(%App{}, %{
           client_name: "client",
-          scopes: ["scope"],
+          scopes: ["read", "write"],
           redirect_uris: "url"
         })
       )
 
-    user = insert(:user)
-
-    {:ok, auth} = Authorization.create_authorization(app, user)
-
-    assert auth.user_id == user.id
-    assert auth.app_id == app.id
-    assert String.length(auth.token) > 10
-    assert auth.used == false
+    %{app: app}
   end
 
-  test "use up a token" do
-    {:ok, app} =
-      Repo.insert(
-        App.register_changeset(%App{}, %{
-          client_name: "client",
-          scopes: ["scope"],
-          redirect_uris: "url"
-        })
-      )
+  test "create an authorization token for a valid app", %{app: app} do
+    user = insert(:user)
 
+    {:ok, auth1} = Authorization.create_authorization(app, user)
+    assert auth1.scopes == app.scopes
+
+    {:ok, auth2} = Authorization.create_authorization(app, user, ["read"])
+    assert auth2.scopes == ["read"]
+
+    for auth <- [auth1, auth2] do
+      assert auth.user_id == user.id
+      assert auth.app_id == app.id
+      assert String.length(auth.token) > 10
+      assert auth.used == false
+    end
+  end
+
+  test "use up a token", %{app: app} do
     user = insert(:user)
 
     {:ok, auth} = Authorization.create_authorization(app, user)
@@ -61,16 +62,7 @@ test "use up a token" do
     assert {:error, "token expired"} == Authorization.use_token(expired_auth)
   end
 
-  test "delete authorizations" do
-    {:ok, app} =
-      Repo.insert(
-        App.register_changeset(%App{}, %{
-          client_name: "client",
-          scopes: ["scope"],
-          redirect_uris: "url"
-        })
-      )
-
+  test "delete authorizations", %{app: app} do
     user = insert(:user)
 
     {:ok, auth} = Authorization.create_authorization(app, user)
diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs
index ca1c04319..53d83e6e8 100644
--- a/test/web/oauth/oauth_controller_test.exs
+++ b/test/web/oauth/oauth_controller_test.exs
@@ -12,7 +12,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
   test "redirects with oauth authorization" do
     user = insert(:user)
-    app = insert(:oauth_app)
+    app = insert(:oauth_app, scopes: ["read", "write", "follow"])
 
     conn =
       build_conn()
@@ -22,7 +22,7 @@ test "redirects with oauth authorization" do
           "password" => "test",
           "client_id" => app.client_id,
           "redirect_uri" => app.redirect_uris,
-          "scope" => Enum.join(app.scopes, " "),
+          "scope" => "read write",
           "state" => "statepassed"
         }
       })
@@ -33,10 +33,12 @@ test "redirects with oauth authorization" do
     query = URI.parse(target).query |> URI.query_decoder() |> Map.new()
 
     assert %{"state" => "statepassed", "code" => code} = query
-    assert Repo.get_by(Authorization, token: code)
+    auth = Repo.get_by(Authorization, token: code)
+    assert auth
+    assert auth.scopes == ["read", "write"]
   end
 
-  test "correctly handles wrong credentials", %{conn: conn} do
+  test "returns 401 for wrong credentials", %{conn: conn} do
     user = insert(:user)
     app = insert(:oauth_app)
 
@@ -48,7 +50,8 @@ test "correctly handles wrong credentials", %{conn: conn} do
           "password" => "wrong",
           "client_id" => app.client_id,
           "redirect_uri" => app.redirect_uris,
-          "state" => "statepassed"
+          "state" => "statepassed",
+          "scope" => Enum.join(app.scopes, " ")
         }
       })
       |> html_response(:unauthorized)
@@ -58,14 +61,66 @@ test "correctly handles wrong credentials", %{conn: conn} do
     assert result =~ app.redirect_uris
 
     # Error message
-    assert result =~ "Invalid"
+    assert result =~ "Invalid Username/Password"
+  end
+
+  test "returns 401 for missing scopes", %{conn: conn} do
+    user = insert(:user)
+    app = insert(:oauth_app)
+
+    result =
+      conn
+      |> post("/oauth/authorize", %{
+        "authorization" => %{
+          "name" => user.nickname,
+          "password" => "test",
+          "client_id" => app.client_id,
+          "redirect_uri" => app.redirect_uris,
+          "state" => "statepassed",
+          "scope" => ""
+        }
+      })
+      |> html_response(:unauthorized)
+
+    # Keep the details
+    assert result =~ app.client_id
+    assert result =~ app.redirect_uris
+
+    # Error message
+    assert result =~ "Permissions not specified"
+  end
+
+  test "returns 401 for scopes beyond app scopes", %{conn: conn} do
+    user = insert(:user)
+    app = insert(:oauth_app, scopes: ["read", "write"])
+
+    result =
+      conn
+      |> post("/oauth/authorize", %{
+        "authorization" => %{
+          "name" => user.nickname,
+          "password" => "test",
+          "client_id" => app.client_id,
+          "redirect_uri" => app.redirect_uris,
+          "state" => "statepassed",
+          "scope" => "read write follow"
+        }
+      })
+      |> html_response(:unauthorized)
+
+    # Keep the details
+    assert result =~ app.client_id
+    assert result =~ app.redirect_uris
+
+    # Error message
+    assert result =~ "Permissions not specified"
   end
 
   test "issues a token for an all-body request" do
     user = insert(:user)
-    app = insert(:oauth_app)
+    app = insert(:oauth_app, scopes: ["read", "write"])
 
-    {:ok, auth} = Authorization.create_authorization(app, user)
+    {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
 
     conn =
       build_conn()
@@ -78,15 +133,19 @@ test "issues a token for an all-body request" do
       })
 
     assert %{"access_token" => token} = json_response(conn, 200)
-    assert Repo.get_by(Token, token: token)
+
+    token = Repo.get_by(Token, token: token)
+    assert token
+    assert token.scopes == auth.scopes
   end
 
-  test "issues a token for `password` grant_type with valid credentials" do
+  test "issues a token for `password` grant_type with valid credentials, with full permissions by default" do
     password = "testpassword"
     user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
 
-    app = insert(:oauth_app)
+    app = insert(:oauth_app, scopes: ["read", "write"])
 
+    # Note: "scope" param is intentionally omitted
     conn =
       build_conn()
       |> post("/oauth/token", %{
@@ -98,14 +157,18 @@ test "issues a token for `password` grant_type with valid credentials" do
       })
 
     assert %{"access_token" => token} = json_response(conn, 200)
-    assert Repo.get_by(Token, token: token)
+
+    token = Repo.get_by(Token, token: token)
+    assert token
+    assert token.scopes == app.scopes
   end
 
   test "issues a token for request with HTTP basic auth client credentials" do
     user = insert(:user)
-    app = insert(:oauth_app)
+    app = insert(:oauth_app, scopes: ["scope1", "scope2"])
 
-    {:ok, auth} = Authorization.create_authorization(app, user)
+    {:ok, auth} = Authorization.create_authorization(app, user, ["scope2"])
+    assert auth.scopes == ["scope2"]
 
     app_encoded =
       (URI.encode_www_form(app.client_id) <> ":" <> URI.encode_www_form(app.client_secret))
@@ -121,7 +184,10 @@ test "issues a token for request with HTTP basic auth client credentials" do
       })
 
     assert %{"access_token" => token} = json_response(conn, 200)
-    assert Repo.get_by(Token, token: token)
+
+    token = Repo.get_by(Token, token: token)
+    assert token
+    assert token.scopes == ["scope2"]
   end
 
   test "rejects token exchange with invalid client credentials" do
diff --git a/test/web/oauth/token_test.exs b/test/web/oauth/token_test.exs
index a708e4991..62444a0fa 100644
--- a/test/web/oauth/token_test.exs
+++ b/test/web/oauth/token_test.exs
@@ -11,24 +11,26 @@ defmodule Pleroma.Web.OAuth.TokenTest do
 
   import Pleroma.Factory
 
-  test "exchanges a auth token for an access token" do
+  test "exchanges a auth token for an access token, preserving `scopes`" do
     {:ok, app} =
       Repo.insert(
         App.register_changeset(%App{}, %{
           client_name: "client",
-          scopes: ["scope"],
+          scopes: ["read", "write"],
           redirect_uris: "url"
         })
       )
 
     user = insert(:user)
 
-    {:ok, auth} = Authorization.create_authorization(app, user)
+    {:ok, auth} = Authorization.create_authorization(app, user, ["read"])
+    assert auth.scopes == ["read"]
 
     {:ok, token} = Token.exchange_token(app, auth)
 
     assert token.app_id == app.id
     assert token.user_id == user.id
+    assert token.scopes == auth.scopes
     assert String.length(token.token) > 10
     assert String.length(token.refresh_token) > 10
 
diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/web/twitter_api/twitter_api_controller_test.exs
index 1571ab68e..27b1e878c 100644
--- a/test/web/twitter_api/twitter_api_controller_test.exs
+++ b/test/web/twitter_api/twitter_api_controller_test.exs
@@ -1690,6 +1690,24 @@ test "it lists friend requests" do
       assert [relationship] = json_response(conn, 200)
       assert other_user.id == relationship["id"]
     end
+
+    test "requires 'read' permission", %{conn: conn} do
+      token1 = insert(:oauth_token, scopes: ["write"])
+      token2 = insert(:oauth_token, scopes: ["read"])
+
+      for token <- [token1, token2] do
+        conn =
+          conn
+          |> put_req_header("authorization", "Bearer #{token.token}")
+          |> get("/api/pleroma/friend_requests")
+
+        if token == token1 do
+          assert %{"error" => "Insufficient permissions: read."} == json_response(conn, 403)
+        else
+          assert json_response(conn, 200)
+        end
+      end
+    end
   end
 
   describe "POST /api/pleroma/friendships/approve" do
diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs
index 007d7d8e6..fc762ab18 100644
--- a/test/web/twitter_api/util_controller_test.exs
+++ b/test/web/twitter_api/util_controller_test.exs
@@ -16,6 +16,25 @@ test "it returns HTTP 200", %{conn: conn} do
 
       assert response == "job started"
     end
+
+    test "requires 'follow' permission", %{conn: conn} do
+      token1 = insert(:oauth_token, scopes: ["read", "write"])
+      token2 = insert(:oauth_token, scopes: ["follow"])
+      another_user = insert(:user)
+
+      for token <- [token1, token2] do
+        conn =
+          conn
+          |> put_req_header("authorization", "Bearer #{token.token}")
+          |> post("/api/pleroma/follow_import", %{"list" => "#{another_user.ap_id}"})
+
+        if token == token1 do
+          assert %{"error" => "Insufficient permissions: follow."} == json_response(conn, 403)
+        else
+          assert json_response(conn, 200)
+        end
+      end
+    end
   end
 
   describe "POST /api/pleroma/blocks_import" do