Skip to content

Commit 70fe9f0

Browse files
committed
parse urls when validating redirect targets
simplifies check for redirects to external hosts
1 parent 9e5c6ef commit 70fe9f0

File tree

2 files changed

+52
-3
lines changed

2 files changed

+52
-3
lines changed

notebook/auth/login.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,15 @@ def _redirect_safe(self, url, default=None):
3939
"""
4040
if default is None:
4141
default = self.base_url
42-
if not url.startswith(self.base_url):
42+
parsed = urlparse(url)
43+
if parsed.netloc or not (parsed.path + '/').startswith(self.base_url):
4344
# require that next_url be absolute path within our path
4445
allow = False
4546
# OR pass our cross-origin check
46-
if '://' in url:
47+
if parsed.netloc:
4748
# if full URL, run our cross-origin check:
48-
parsed = urlparse(url.lower())
4949
origin = '%s://%s' % (parsed.scheme, parsed.netloc)
50+
origin = origin.lower()
5051
if self.allow_origin:
5152
allow = self.allow_origin == origin
5253
elif self.allow_origin_pat:

notebook/auth/tests/test_login.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""Tests for login redirects"""
2+
3+
import requests
4+
from tornado.httputil import url_concat
5+
6+
from notebook.tests.launchnotebook import NotebookTestBase
7+
8+
9+
class LoginTest(NotebookTestBase):
10+
def login(self, next):
11+
first = requests.get(self.base_url() + "login")
12+
first.raise_for_status()
13+
resp = requests.post(
14+
url_concat(
15+
self.base_url() + "login",
16+
{'next': next},
17+
),
18+
allow_redirects=False,
19+
data={
20+
"password": self.token,
21+
"_xsrf": first.cookies.get("_xsrf", ""),
22+
},
23+
cookies=first.cookies,
24+
)
25+
resp.raise_for_status()
26+
return resp.headers['Location']
27+
28+
def test_next_bad(self):
29+
for bad_next in (
30+
"//some-host",
31+
"//host" + self.url_prefix + "tree",
32+
"https://google.com",
33+
"/absolute/not/base_url",
34+
):
35+
url = self.login(next=bad_next)
36+
self.assertEqual(url, self.url_prefix)
37+
assert url
38+
39+
def test_next_ok(self):
40+
for next_path in (
41+
"tree/",
42+
"//" + self.url_prefix + "tree",
43+
"notebooks/notebook.ipynb",
44+
"tree//something",
45+
):
46+
expected = self.url_prefix + next_path
47+
actual = self.login(next=expected)
48+
self.assertEqual(actual, expected)

0 commit comments

Comments
 (0)