Skip to content

Commit ab79144

Browse files
rgbkrkminrk
authored andcommitted
Backport PR #1938: don't check origin on token-authenticated requests
adds LoginHandler.should_check_origin classmethod API While testing, I noticed that we were checking origin and authentication on CSP violation reports, but browsers are sending CSP reports with no Origin, meaning that no CSP report will ever be accepted. That doesn't make sense to me, so I disabled both - a CSP report can now be made from *anywhere* (no auth, no check). rgbkrk does that sound right to you? Or should it be authenticated but no origin check? Signed-off-by: Min RK <[email protected]>
1 parent 45503a8 commit ab79144

File tree

4 files changed

+46
-11
lines changed

4 files changed

+46
-11
lines changed

notebook/auth/login.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,18 @@ def get_user_token(cls, handler):
114114
user_token = m.group(1)
115115
return user_token
116116

117+
@classmethod
118+
def should_check_origin(cls, handler):
119+
"""Should the Handler check for CORS origin validation?
120+
121+
Origin check should be skipped for token-authenticated requests.
122+
"""
123+
if getattr(handler, '_user_id', None) is None:
124+
# ensure get_user has been called, so we know if we're token-authenticated
125+
handler.get_current_user()
126+
token_authenticated = getattr(handler, '_token_authenticated', False)
127+
return not token_authenticated
128+
117129
@classmethod
118130
def get_user(cls, handler):
119131
"""Called by handlers.get_current_user for identifying the current user.
@@ -137,17 +149,23 @@ def get_user(cls, handler):
137149
# check login token from URL argument or Authorization header
138150
user_token = cls.get_user_token(handler)
139151
one_time_token = handler.one_time_token
152+
authenticated = False
140153
if user_token == token:
141154
# token-authenticated, set the login cookie
142155
handler.log.info("Accepting token-authenticated connection from %s", handler.request.remote_ip)
143-
user_id = uuid.uuid4().hex
144-
cls.set_login_cookie(handler, user_id)
145-
if one_time_token and user_token == one_time_token:
146-
# one-time token-authenticated, only allow this token once
156+
authenticated = True
157+
elif one_time_token and user_token == one_time_token:
158+
# one-time-token-authenticated, only allow this token once
147159
handler.settings.pop('one_time_token', None)
148160
handler.log.info("Accepting one-time-token-authenticated connection from %s", handler.request.remote_ip)
161+
authenticated = True
162+
if authenticated:
149163
user_id = uuid.uuid4().hex
150164
cls.set_login_cookie(handler, user_id)
165+
# Record that we've been authenticated with a token.
166+
# Used in should_check_origin above.
167+
handler._token_authenticated = True
168+
151169
# cache value for future retrievals on the same request
152170
handler._user_id = user_id
153171
return user_id

notebook/base/handlers.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,16 @@ def get_current_user(self):
7979
return 'anonymous'
8080
return self.login_handler.get_user(self)
8181

82+
def skip_check_origin(self):
83+
"""Ask my login_handler if I should skip the origin_check
84+
85+
For example: in the default LoginHandler, if a request is token-authenticated,
86+
origin checking should be skipped.
87+
"""
88+
if self.login_handler is None or not hasattr(self.login_handler, 'should_check_origin'):
89+
return False
90+
return not self.login_handler.should_check_origin(self)
91+
8292
@property
8393
def cookie_name(self):
8494
default_cookie_name = non_alphanum.sub('-', 'username-{}'.format(
@@ -264,8 +274,9 @@ def check_origin(self, origin_to_satisfy_tornado=""):
264274
Copied from WebSocket with changes:
265275
266276
- allow unspecified host/origin (e.g. scripts)
277+
- allow token-authenticated requests
267278
"""
268-
if self.allow_origin == '*':
279+
if self.allow_origin == '*' or self.skip_check_origin():
269280
return True
270281

271282
host = self.request.headers.get("Host")
@@ -292,8 +303,8 @@ def check_origin(self, origin_to_satisfy_tornado=""):
292303
# No CORS headers deny the request
293304
allow = False
294305
if not allow:
295-
self.log.warn("Blocking Cross Origin API request. Origin: %s, Host: %s",
296-
origin, host,
306+
self.log.warning("Blocking Cross Origin API request for %s. Origin: %s, Host: %s",
307+
self.request.path, origin, host,
297308
)
298309
return allow
299310

notebook/base/zmqhandlers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,9 @@ def check_origin(self, origin=None):
126126
127127
Tornado >= 4 calls this method automatically, raising 403 if it returns False.
128128
"""
129-
if self.allow_origin == '*':
129+
130+
if self.allow_origin == '*' or (
131+
hasattr(self, 'skip_check_origin') and self.skip_check_origin()):
130132
return True
131133

132134
host = self.request.headers.get("Host")

notebook/services/security/handlers.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,23 @@
33
# Copyright (c) Jupyter Development Team.
44
# Distributed under the terms of the Modified BSD License.
55

6-
from tornado import gen, web
6+
from tornado import web
77

88
from ...base.handlers import APIHandler, json_errors
99
from . import csp_report_uri
1010

1111
class CSPReportHandler(APIHandler):
1212
'''Accepts a content security policy violation report'''
13+
14+
def skip_origin_check(self):
15+
"""Don't check origin when reporting origin-check violations!"""
16+
return True
17+
1318
@json_errors
1419
@web.authenticated
1520
def post(self):
1621
'''Log a content security policy violation report'''
17-
csp_report = self.get_json_body()
18-
self.log.warn("Content security violation: %s",
22+
self.log.warning("Content security violation: %s",
1923
self.request.body.decode('utf8', 'replace'))
2024

2125
default_handlers = [

0 commit comments

Comments
 (0)