X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=test%2Fpleroma%2Fweb%2Fo_auth%2Fo_auth_controller_test.exs;h=bf47afed820cf05a39562b9298784fe302b223a7;hb=8a4437d2bee6ae5f07935a8a6471e8c8dac7f3b1;hp=a00df8cc7e6e191cdbf0f87b622ff37d0e03edea;hpb=a326a56371602e05315c1a5a509ee1fa887dbb1c;p=akkoma diff --git a/test/pleroma/web/o_auth/o_auth_controller_test.exs b/test/pleroma/web/o_auth/o_auth_controller_test.exs index a00df8cc7..c996a403c 100644 --- a/test/pleroma/web/o_auth/o_auth_controller_test.exs +++ b/test/pleroma/web/o_auth/o_auth_controller_test.exs @@ -1,11 +1,13 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors +# Copyright © 2017-2021 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.OAuthControllerTest do use Pleroma.Web.ConnCase + import Pleroma.Factory + alias Pleroma.Helpers.AuthHelper alias Pleroma.MFA alias Pleroma.MFA.TOTP alias Pleroma.Repo @@ -81,7 +83,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do 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 @@ -115,7 +117,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", "provider" => "twitter", - "state" => Poison.encode!(state_params) + "state" => Jason.encode!(state_params) } ) @@ -147,7 +149,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", "provider" => "twitter", - "state" => Poison.encode!(state_params) + "state" => Jason.encode!(state_params) } ) @@ -178,7 +180,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", "provider" => "twitter", - "state" => Poison.encode!(state_params) + "state" => Jason.encode!(state_params) } ) @@ -314,7 +316,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do app: app, conn: conn } do - user = insert(:user, password_hash: Pbkdf2.hash_pwd_salt("testpassword")) + user = insert(:user, password_hash: Pleroma.Password.hash_pwd_salt("testpassword")) registration = insert(:registration, user: nil) redirect_uri = OAuthController.default_redirect_uri(app) @@ -345,7 +347,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do app: app, conn: conn } do - user = insert(:user, password_hash: Pbkdf2.hash_pwd_salt("testpassword")) + user = insert(:user, password_hash: Pleroma.Password.hash_pwd_salt("testpassword")) registration = insert(:registration, user: nil) unlisted_redirect_uri = "http://cross-site-request.com" @@ -454,7 +456,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do conn = conn - |> put_session(:oauth_token, token.token) + |> AuthHelper.put_session_token(token.token) |> get( "/oauth/authorize", %{ @@ -478,7 +480,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do conn = conn - |> put_session(:oauth_token, token.token) + |> AuthHelper.put_session_token(token.token) |> get( "/oauth/authorize", %{ @@ -492,6 +494,129 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do 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, @@ -501,7 +626,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do conn = conn - |> put_session(:oauth_token, token.token) + |> AuthHelper.put_session_token(token.token) |> get( "/oauth/authorize", %{ @@ -527,7 +652,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do conn = conn - |> put_session(:oauth_token, token.token) + |> AuthHelper.put_session_token(token.token) |> get( "/oauth/authorize", %{ @@ -551,7 +676,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do conn = conn - |> put_session(:oauth_token, token.token) + |> AuthHelper.put_session_token(token.token) |> get( "/oauth/authorize", %{ @@ -568,45 +693,147 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do 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 admin users" do app_scopes = ["read", "write", "admin", "secret_scope"] app = insert(:oauth_app, scopes: app_scopes) redirect_uri = OAuthController.default_redirect_uri(app) - - non_admin = insert(:user, is_admin: false) + 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 to moderators" do + 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_moderator: true) # 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" => 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 "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) + scopes_subset = ["read:subscope", "write", "admin", "admin:metrics"] + + # In case scope param is missing, expecting _all_ app-supported scopes to be granted + 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 + + 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:subscope", "write"] + end + + test "authorize from cookie" do + user = insert(:user) + app = insert(:oauth_app) + oauth_token = insert(:oauth_token, user: user, app: app) + redirect_uri = OAuthController.default_redirect_uri(app) + + conn = + build_conn() + |> Plug.Session.call(Plug.Session.init(@session_opts)) + |> fetch_session() + |> AuthHelper.put_session_token(oauth_token.token) + |> post( + "/oauth/authorize", + %{ + "authorization" => %{ + "name" => user.nickname, + "client_id" => app.client_id, + "redirect_uri" => redirect_uri, + "scope" => app.scopes, + "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 == app.scopes end test "redirect to on two-factor auth page" do @@ -671,33 +898,6 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do assert result =~ "Invalid Username/Password" end - test "returns 401 for missing scopes" do - user = insert(:user, is_admin: false) - app = insert(:oauth_app, scopes: ["read", "write", "admin"]) - redirect_uri = OAuthController.default_redirect_uri(app) - - result = - build_conn() - |> post("/oauth/authorize", %{ - "authorization" => %{ - "name" => user.nickname, - "password" => "test", - "client_id" => app.client_id, - "redirect_uri" => redirect_uri, - "state" => "statepassed", - "scope" => "" - } - }) - |> html_response(:unauthorized) - - # Keep the details - assert result =~ app.client_id - assert result =~ redirect_uri - - # Error message - assert result =~ "This action is outside the authorized scopes" - end - test "returns 401 for scopes beyond app scopes hierarchy", %{conn: conn} do user = insert(:user) app = insert(:oauth_app, scopes: ["read", "write"]) @@ -722,7 +922,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do 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 @@ -753,7 +953,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" - user = insert(:user, password_hash: Pbkdf2.hash_pwd_salt(password)) + user = insert(:user, password_hash: Pleroma.Password.hash_pwd_salt(password)) app = insert(:oauth_app, scopes: ["read", "write"]) @@ -768,10 +968,12 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do "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.id == id + assert token.token == access_token assert token.scopes == app.scopes end @@ -781,7 +983,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do user = insert(:user, - password_hash: Pbkdf2.hash_pwd_salt(password), + password_hash: Pleroma.Password.hash_pwd_salt(password), multi_factor_authentication_settings: %MFA.Settings{ enabled: true, totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} @@ -886,12 +1088,12 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest 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} = - insert(:user, password_hash: Pbkdf2.hash_pwd_salt(password)) - |> User.confirmation_changeset(need_confirmation: true) + insert(:user, password_hash: Pleroma.Password.hash_pwd_salt(password)) + |> User.confirmation_changeset(set_confirmation: false) |> User.update_and_set_cache() refute Pleroma.User.account_status(user) == :active @@ -918,8 +1120,8 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do user = insert(:user, - password_hash: Pbkdf2.hash_pwd_salt(password), - deactivated: true + password_hash: Pleroma.Password.hash_pwd_salt(password), + is_active: false ) app = insert(:oauth_app) @@ -946,7 +1148,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do user = insert(:user, - password_hash: Pbkdf2.hash_pwd_salt(password), + password_hash: Pleroma.Password.hash_pwd_salt(password), password_reset_pending: true ) @@ -970,13 +1172,13 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest 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_hash: Pbkdf2.hash_pwd_salt(password), - confirmation_pending: true + password_hash: Pleroma.Password.hash_pwd_salt(password), + is_confirmed: false ) app = insert(:oauth_app, scopes: ["read", "write"]) @@ -1001,7 +1203,11 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do 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.hash_pwd_salt(password), + is_approved: false + ) refute Pleroma.User.account_status(user) == :active @@ -1045,7 +1251,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 - 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"]) @@ -1068,7 +1274,6 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do %{ "scope" => "write", "token_type" => "Bearer", - "expires_in" => 600, "access_token" => _, "refresh_token" => _, "me" => ^ap_id @@ -1085,7 +1290,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest 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"]) @@ -1108,7 +1313,6 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do %{ "scope" => "write", "token_type" => "Bearer", - "expires_in" => 600, "access_token" => _, "refresh_token" => _, "me" => ^ap_id @@ -1191,7 +1395,6 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do %{ "scope" => "write", "token_type" => "Bearer", - "expires_in" => 600, "access_token" => _, "refresh_token" => _, "me" => ^ap_id @@ -1219,8 +1422,43 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do 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() + |> AuthHelper.put_session_token(oauth_token.token) + |> post("/oauth/revoke", %{"token" => oauth_token.token}) + + assert json_response(conn, 200) + + refute AuthHelper.get_session_token(conn) + 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() + |> AuthHelper.put_session_token(oauth_token.token) + |> post("/oauth/revoke", %{"token" => other_app_oauth_token.token}) + + assert json_response(conn, 200) + + assert AuthHelper.get_session_token(conn) == oauth_token.token + 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", %{})