1"""Tests for login redirects""" 2from functools import partial 3from urllib.parse import urlencode 4 5import pytest 6from tornado.httpclient import HTTPClientError 7from tornado.httputil import parse_cookie 8from tornado.httputil import url_concat 9 10from jupyter_server.utils import url_path_join 11 12 13# override default config to ensure a non-empty base url is used 14@pytest.fixture 15def jp_base_url(): 16 return "/a%40b/" 17 18 19@pytest.fixture 20def jp_server_config(jp_base_url): 21 return { 22 "ServerApp": { 23 "base_url": jp_base_url, 24 }, 25 } 26 27 28async def _login(jp_serverapp, http_server_client, jp_base_url, next): 29 # first: request login page with no creds 30 login_url = url_path_join(jp_base_url, "login") 31 first = await http_server_client.fetch(login_url) 32 cookie_header = first.headers["Set-Cookie"] 33 cookies = parse_cookie(cookie_header) 34 35 # second, submit login form with credentials 36 try: 37 resp = await http_server_client.fetch( 38 url_concat(login_url, {"next": next}), 39 method="POST", 40 body=urlencode( 41 { 42 "password": jp_serverapp.token, 43 "_xsrf": cookies.get("_xsrf", ""), 44 } 45 ), 46 headers={"Cookie": cookie_header}, 47 follow_redirects=False, 48 ) 49 except HTTPClientError as e: 50 if e.code != 302: 51 raise 52 return e.response.headers["Location"] 53 else: 54 assert resp.code == 302, "Should have returned a redirect!" 55 56 57@pytest.fixture 58def login(jp_serverapp, http_server_client, jp_base_url): 59 """Fixture to return a function to login to a Jupyter server 60 61 by submitting the login page form 62 """ 63 yield partial(_login, jp_serverapp, http_server_client, jp_base_url) 64 65 66@pytest.mark.parametrize( 67 "bad_next", 68 ( 69 r"\\tree", 70 "//some-host", 71 "//host{base_url}tree", 72 "https://google.com", 73 "/absolute/not/base_url", 74 ), 75) 76async def test_next_bad(login, jp_base_url, bad_next): 77 bad_next = bad_next.format(base_url=jp_base_url) 78 url = await login(bad_next) 79 assert url == jp_base_url 80 81 82@pytest.mark.parametrize( 83 "next_path", 84 ( 85 "tree/", 86 "//{base_url}tree", 87 "notebooks/notebook.ipynb", 88 "tree//something", 89 ), 90) 91async def test_next_ok(login, jp_base_url, next_path): 92 next_path = next_path.format(base_url=jp_base_url) 93 expected = jp_base_url + next_path 94 actual = await login(next=expected) 95 assert actual == expected 96