import base64 import json import time from urllib.parse import unquote import pytest from flask import url_for, session from auth_proxy.helpers import add_url_params from auth_proxy.views import ALLOWED_FORWARD_PARAMS from tests.auth_proxy.pytest_helpers import get_cookie class TestViews: FAKE_HOST = "auth_proxy.test" LOGIN_URL = "https://atlas_um.test/usm/login" LOGOUT_URL = "https://atlas_um.test/usm/logout" REDIRECT_URL = "https://product.test" @pytest.fixture def app_config(self): def inner(app): app.config["SERVER_NAME"] = self.FAKE_HOST app.config["LOGIN_URL"] = self.LOGIN_URL app.config["LOGOUT_URL"] = self.LOGOUT_URL app.config["ALLOWED_REDIRECT_URLS"] = self.REDIRECT_URL return inner def test_login_without_referrer(self, app): login_url_with_proxy = add_url_params( self.LOGIN_URL, {"auth_proxy": f"http://{self.FAKE_HOST}/process"} ) url = url_for("proxy.login") with app.test_client() as client: resp = client.get(url) assert resp.status_code == 302 assert login_url_with_proxy in resp.location assert session[app.config.get("REFERRER_SESSION_NAME")] is None def test_login_with_referrer(self, app, faker): test_referrer = faker.url() login_url_with_proxy = add_url_params( self.LOGIN_URL, {"auth_proxy": f"http://{self.FAKE_HOST}/process"} ) url = url_for("proxy.login") with app.test_client() as client: resp = client.get(url, headers={"Referer": test_referrer}) assert resp.status_code == 302 assert login_url_with_proxy in resp.location assert ( session[app.config.get("REFERRER_SESSION_NAME")] == test_referrer ) def test_login_with_next(self, app, faker): test_next = faker.url() login_url_with_proxy = add_url_params( self.LOGIN_URL, {"auth_proxy": f"http://{self.FAKE_HOST}/process"} ) url = url_for("proxy.login", next=test_next) with app.test_client() as client: resp = client.get(url) assert resp.status_code == 302 assert login_url_with_proxy in resp.location assert ( session[app.config.get("REFERRER_SESSION_NAME")] == test_next ) def test_login_token_redirect_url(self, app, faker): test_redirect_url = self.REDIRECT_URL login_url_with_proxy = add_url_params( self.LOGIN_URL, {"auth_proxy": f"http://{self.FAKE_HOST}/process"} ) url = url_for( "proxy.login", redirect_uri=test_redirect_url, response_type="token", ) with app.test_client() as client: resp = client.get(url) assert resp.status_code == 302 assert login_url_with_proxy in resp.location assert ( session[app.config.get("REDIRECT_URL_SESSION_NAME")] == test_redirect_url ) def test_login_token_invalid_redirect_url(self, app, faker): test_redirect_url = faker.url() url = url_for( "proxy.login", redirect_uri=test_redirect_url, response_type="token", ) with app.test_client() as client: resp = client.get(url) assert resp.status_code == 400 url = url_for( "proxy.login", response_type="token", ) with app.test_client() as client: resp = client.get(url) assert resp.status_code == 400 def test_forward_params(self, app, faker): forward_params = {k: faker.pystr() for k in ALLOWED_FORWARD_PARAMS} expected_url = add_url_params( self.LOGIN_URL, { **forward_params, "auth_proxy": f"http://{self.FAKE_HOST}/process", }, ) url = url_for( "proxy.login", test_not_allowed_param=faker.pystr(), **forward_params, ) with app.test_client() as client: resp = client.get(url) assert resp.status_code == 302 assert expected_url in resp.location assert session[app.config.get("REFERRER_SESSION_NAME")] is None def test_logout_next(self, app, faker): login_url_with_proxy = add_url_params( self.LOGOUT_URL, {"auth_proxy": f"http://{self.FAKE_HOST}/process"} ) test_next = faker.url() url = url_for("proxy.logout", next=test_next) with app.test_client() as client: resp = client.get(url) assert ( session[app.config.get("REFERRER_SESSION_NAME")] == test_next ) assert resp.status_code == 302 assert login_url_with_proxy in resp.location def test_logout_redirect_url(self, app, faker): login_url_with_proxy = add_url_params( self.LOGOUT_URL, {"auth_proxy": f"http://{self.FAKE_HOST}/process"} ) test_redirect_url = faker.url() url = url_for("proxy.logout", redirect_uri=test_redirect_url) with app.test_client() as client: resp = client.get(url) assert ( session[app.config.get("REFERRER_SESSION_NAME")] == test_redirect_url ) assert resp.status_code == 302 assert login_url_with_proxy in resp.location def test_process_without_next_and_referrer(self, app): url = url_for("proxy.process") with app.test_client() as client: resp = client.post(url) assert resp.status_code == 400 def test_process_with_referrer(self, app, faker, mocker): mocker.patch("auth_proxy.views.get_public_key") mocker.patch("jwt.decode") mocked_get_allowed_root_domain = mocker.patch( "auth_proxy.views.get_allowed_root_domain" ) mocked_get_allowed_root_domain.return_value = f".{self.FAKE_HOST}" test_access_token = faker.pystr() test_refresh_token = faker.pystr() test_referrer = faker.url() url = url_for("proxy.process") with app.test_client() as client: with client.session_transaction() as session: session[ app.config.get("REFERRER_SESSION_NAME") ] = test_referrer resp = client.post( url, data={ "access_token": test_access_token, "refresh_token": test_refresh_token, }, ) assert resp.status_code == 302 assert resp.location == test_referrer token_cookie = get_cookie( resp, app.config.get("DNA_BEARER_TOKEN_COOKIE_NAME") ) assert ( token_cookie.get(app.config.get("DNA_BEARER_TOKEN_COOKIE_NAME")) == test_access_token ) assert token_cookie.get("Domain") == f".{self.FAKE_HOST}" assert "HttpOnly" in token_cookie assert "Secure" in token_cookie assert ( int(token_cookie["Max-Age"]) == app.config["TOKEN_COOKIES_MAX_AGE"] ) token_cookie = get_cookie( resp, app.config.get("DNA_REFRESH_TOKEN_COOKIE_NAME") ) assert ( token_cookie.get(app.config.get("DNA_REFRESH_TOKEN_COOKIE_NAME")) == test_refresh_token ) assert token_cookie.get("Domain") == f".{self.FAKE_HOST}" assert "HttpOnly" in token_cookie assert "Secure" in token_cookie assert ( int(token_cookie["Max-Age"]) == app.config["TOKEN_COOKIES_MAX_AGE"] ) def test_process_with_next(self, app, faker, mocker): mocker.patch("auth_proxy.views.get_public_key") mocker.patch("jwt.decode") mocked_get_allowed_root_domain = mocker.patch( "auth_proxy.views.get_allowed_root_domain" ) mocked_get_allowed_root_domain.return_value = f".{self.FAKE_HOST}" test_access_token = faker.pystr() test_refresh_token = faker.pystr() test_next = faker.url() url = url_for("proxy.process") with app.test_client() as client: resp = client.post( url, data={ "access_token": test_access_token, "refresh_token": test_refresh_token, "next": test_next, }, ) assert resp.status_code == 302 assert resp.location == test_next token_cookie = get_cookie( resp, app.config.get("DNA_BEARER_TOKEN_COOKIE_NAME") ) assert ( token_cookie.get(app.config.get("DNA_BEARER_TOKEN_COOKIE_NAME")) == test_access_token ) assert token_cookie.get("Domain") == f".{self.FAKE_HOST}" assert "HttpOnly" in token_cookie assert "Secure" in token_cookie assert ( int(token_cookie["Max-Age"]) == app.config["TOKEN_COOKIES_MAX_AGE"] ) token_cookie = get_cookie( resp, app.config.get("DNA_REFRESH_TOKEN_COOKIE_NAME") ) assert ( token_cookie.get(app.config.get("DNA_REFRESH_TOKEN_COOKIE_NAME")) == test_refresh_token ) assert token_cookie.get("Domain") == f".{self.FAKE_HOST}" assert "HttpOnly" in token_cookie assert "Secure" in token_cookie assert ( int(token_cookie["Max-Age"]) == app.config["TOKEN_COOKIES_MAX_AGE"] ) def test_process_with_referrer_no_token(self, app, faker, mocker): mocker.patch("auth_proxy.views.get_public_key") mocker.patch("jwt.decode") mocked_get_allowed_root_domain = mocker.patch( "auth_proxy.views.get_allowed_root_domain" ) mocked_get_allowed_root_domain.return_value = f".{self.FAKE_HOST}" test_referrer = faker.url() url = url_for("proxy.process") with app.test_client() as client: with client.session_transaction() as session: session[ app.config.get("REFERRER_SESSION_NAME") ] = test_referrer resp = client.post( url, data={}, ) assert resp.status_code == 302 assert resp.location == test_referrer def test_process_with_next_no_token(self, app, faker, mocker): mocker.patch("auth_proxy.views.get_public_key") mocker.patch("jwt.decode") mocked_get_allowed_root_domain = mocker.patch( "auth_proxy.views.get_allowed_root_domain" ) mocked_get_allowed_root_domain.return_value = f".{self.FAKE_HOST}" test_next = faker.url() url = url_for("proxy.process") with app.test_client() as client: resp = client.post( url, data={"next": test_next}, ) assert resp.status_code == 302 assert resp.location == test_next def test_process_with_redirect_url(self, app, faker, mocker): initial_expires = time.time() + 1200 fake_time = faker.pyint() fake_state = faker.pystr() mocker.patch("time.time").return_value = fake_time mocker.patch("auth_proxy.views.get_public_key") mocked_decode = mocker.patch("jwt.decode") mocked_decode.return_value = {"exp": initial_expires} mocked_get_allowed_root_domain = mocker.patch( "auth_proxy.views.get_allowed_root_domain" ) mocked_get_allowed_root_domain.return_value = f".{self.FAKE_HOST}" test_access_token = faker.pystr() test_refresh_token = faker.pystr() test_redirect_url = self.REDIRECT_URL payload = { "access_token": test_access_token, "refresh_token": test_refresh_token, "token_type": "Bearer", "expires_in": initial_expires - fake_time, } encoded_payload = base64.urlsafe_b64encode( json.dumps(payload).encode() ) expected_location = ( f"{unquote(test_redirect_url)}?" f"state={fake_state}#{encoded_payload.decode()}" ) url = url_for("proxy.process") with app.test_client() as client: with client.session_transaction() as session: session[ app.config.get("REDIRECT_URL_SESSION_NAME") ] = test_redirect_url session[app.config.get("STATE_SESSION_NAME")] = fake_state resp = client.post( url, data={ "access_token": test_access_token, "refresh_token": test_refresh_token, }, ) assert resp.status_code == 302 assert resp.location == expected_location token_cookie = get_cookie( resp, app.config.get("DNA_BEARER_TOKEN_COOKIE_NAME") ) assert ( token_cookie.get(app.config.get("DNA_BEARER_TOKEN_COOKIE_NAME")) == test_access_token ) assert token_cookie.get("Domain") == f".{self.FAKE_HOST}" assert "HttpOnly" in token_cookie assert "Secure" in token_cookie assert ( int(token_cookie["Max-Age"]) == app.config["TOKEN_COOKIES_MAX_AGE"] ) token_cookie = get_cookie( resp, app.config.get("DNA_REFRESH_TOKEN_COOKIE_NAME") ) assert ( token_cookie.get(app.config.get("DNA_REFRESH_TOKEN_COOKIE_NAME")) == test_refresh_token ) assert token_cookie.get("Domain") == f".{self.FAKE_HOST}" assert "HttpOnly" in token_cookie assert "Secure" in token_cookie assert ( int(token_cookie["Max-Age"]) == app.config["TOKEN_COOKIES_MAX_AGE"] ) def test_process_with_invalid_redirect_url(self, app, faker, mocker): initial_expires = time.time() + 1200 fake_time = faker.pyint() fake_state = faker.pystr() mocker.patch("time.time").return_value = fake_time mocker.patch("auth_proxy.views.get_public_key") mocked_decode = mocker.patch("jwt.decode") mocked_decode.return_value = {"exp": initial_expires} mocked_get_allowed_root_domain = mocker.patch( "auth_proxy.views.get_allowed_root_domain" ) mocked_get_allowed_root_domain.return_value = f".{self.FAKE_HOST}" test_access_token = faker.pystr() test_refresh_token = faker.pystr() test_redirect_url = faker.url() url = url_for("proxy.process") with app.test_client() as client: with client.session_transaction() as session: session[ app.config.get("REDIRECT_URL_SESSION_NAME") ] = test_redirect_url session[app.config.get("STATE_SESSION_NAME")] = fake_state resp = client.post( url, data={ "access_token": test_access_token, "refresh_token": test_refresh_token, }, ) assert resp.status_code == 401 def test_token_success(self, app, faker, mocker): expires_in = 1200 mocker.patch("auth_proxy.views.get_public_key") mocked_decode = mocker.patch("jwt.decode") mocked_decode.return_value = {"exp": int(time.time()) + expires_in} fake_token = faker.pystr() url = url_for("proxy.token") with app.test_client() as client: client.set_cookie( f".{self.FAKE_HOST}", app.config["DNA_BEARER_TOKEN_COOKIE_NAME"], fake_token, httponly=True, ) resp = client.get(url) assert resp.status_code == 200 assert resp.json["access_token"] == fake_token assert resp.json["token_type"] == "Bearer" assert resp.json["expires_in"] == expires_in def test_token_failure(self, app, mocker): mocker.patch("auth_proxy.views.get_public_key") url = url_for("proxy.token") with app.test_client() as client: resp = client.get(url) assert resp.status_code == 401 @pytest.mark.parametrize( "token_source", [ "cookie", "body", ], ) def test_token_refresh_success(self, app, faker, mocker, token_source): test_refresh_token = faker.pystr() test_tokens_data = { "access_token": faker.pystr(), "refresh_token": faker.pystr(), "token_type": "Bearer", "expires_in": faker.pyint(), } mocked_post = mocker.patch("requests.post") mocked_post.return_value.json.return_value = test_tokens_data mocked_get_allowed_root_domain = mocker.patch( "auth_proxy.views.get_allowed_root_domain" ) mocked_get_allowed_root_domain.return_value = f".{self.FAKE_HOST}" url = url_for("proxy.refresh_token_view") with app.test_client() as client: if token_source == "cookie": client.set_cookie( self.FAKE_HOST, app.config.get("DNA_REFRESH_TOKEN_COOKIE_NAME"), test_refresh_token, httponly=True, ) data = {} else: data = {"refresh_token": test_refresh_token} resp = client.post(url, data=data) assert resp.status_code == 200 assert resp.json == test_tokens_data bearer_cookie = get_cookie( resp, app.config.get("DNA_BEARER_TOKEN_COOKIE_NAME") ) assert ( bearer_cookie.get(app.config.get("DNA_BEARER_TOKEN_COOKIE_NAME")) == test_tokens_data["access_token"] ) assert "HttpOnly" in bearer_cookie assert "Secure" in bearer_cookie assert bearer_cookie.get("Domain") == f".{self.FAKE_HOST}" assert ( int(bearer_cookie["Max-Age"]) == app.config["TOKEN_COOKIES_MAX_AGE"] ) refresh_cookie = get_cookie( resp, app.config.get("DNA_REFRESH_TOKEN_COOKIE_NAME") ) assert ( refresh_cookie.get(app.config.get("DNA_REFRESH_TOKEN_COOKIE_NAME")) == test_tokens_data["refresh_token"] ) assert "HttpOnly" in refresh_cookie assert refresh_cookie.get("Domain") == f".{self.FAKE_HOST}" assert "Secure" in refresh_cookie assert ( int(refresh_cookie["Max-Age"]) == app.config["TOKEN_COOKIES_MAX_AGE"] ) def test_token_refresh_failure_no_token(self, app, faker): url = url_for("proxy.refresh_token_view") with app.test_client() as client: resp = client.post(url) assert resp.status_code == 401 assert resp.json is None bearer_cookie = get_cookie( resp, app.config.get("DNA_BEARER_TOKEN_COOKIE_NAME") ) assert bearer_cookie is None refresh_cookie = get_cookie( resp, app.config.get("DNA_REFRESH_TOKEN_COOKIE_NAME") ) assert refresh_cookie is None def test_token_refresh_failure_wrong_token(self, app, faker, mocker): test_refresh_token = faker.pystr() url = url_for("proxy.refresh_token_view") mocked_post = mocker.patch("requests.post") mocked_post.return_value.ok = False with app.test_client() as client: resp = client.post(url, data={"refresh_token": test_refresh_token}) assert resp.status_code == 401 assert resp.json is None assert resp.json is None bearer_cookie = get_cookie( resp, app.config.get("DNA_BEARER_TOKEN_COOKIE_NAME") ) assert bearer_cookie is None refresh_cookie = get_cookie( resp, app.config.get("DNA_REFRESH_TOKEN_COOKIE_NAME") ) assert refresh_cookie is None