From 7621fae43ed164559317563dc5e3a34992fe3fd5 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 1 Dec 2016 18:54:22 +0100 Subject: [PATCH 1/3] include PATH in cross-origin blocking log message makes it clearer what's being blocked --- notebook/base/handlers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notebook/base/handlers.py b/notebook/base/handlers.py index b07975fb1..1baceaeaf 100755 --- a/notebook/base/handlers.py +++ b/notebook/base/handlers.py @@ -295,8 +295,8 @@ class IPythonHandler(AuthenticatedHandler): # No CORS headers deny the request allow = False if not allow: - self.log.warning("Blocking Cross Origin API request. Origin: %s, Host: %s", - origin, host, + self.log.warning("Blocking Cross Origin API request for %s. Origin: %s, Host: %s", + self.request.path, origin, host, ) return allow From 910caf56e1dbb711e085cf272707c6f6912f85a0 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 1 Dec 2016 18:56:02 +0100 Subject: [PATCH 2/3] Don't check origin on CSP violation reports still authenticate, though --- notebook/services/security/handlers.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/notebook/services/security/handlers.py b/notebook/services/security/handlers.py index 618bd936e..36da227dd 100644 --- a/notebook/services/security/handlers.py +++ b/notebook/services/security/handlers.py @@ -3,18 +3,22 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -from tornado import gen, web +from tornado import web from ...base.handlers import APIHandler, json_errors from . import csp_report_uri class CSPReportHandler(APIHandler): '''Accepts a content security policy violation report''' + + def skip_origin_check(self): + """Don't check origin when reporting origin-check violations!""" + return True + @json_errors @web.authenticated def post(self): '''Log a content security policy violation report''' - csp_report = self.get_json_body() self.log.warning("Content security violation: %s", self.request.body.decode('utf8', 'replace')) From 1e070a50f599d795fec1fdd4cd97a0d4f936e070 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 1 Dec 2016 18:53:50 +0100 Subject: [PATCH 3/3] don't check origin on token-authenticated requests adds LoginHandler.should_check_origin classmethod API --- notebook/auth/login.py | 26 ++++++++++++++++++++++---- notebook/base/handlers.py | 13 ++++++++++++- notebook/base/zmqhandlers.py | 4 +++- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/notebook/auth/login.py b/notebook/auth/login.py index c2dcd5962..2484398ab 100644 --- a/notebook/auth/login.py +++ b/notebook/auth/login.py @@ -114,6 +114,18 @@ class LoginHandler(IPythonHandler): user_token = m.group(1) return user_token + @classmethod + def should_check_origin(cls, handler): + """Should the Handler check for CORS origin validation? + + Origin check should be skipped for token-authenticated requests. + """ + if getattr(handler, '_user_id', None) is None: + # ensure get_user has been called, so we know if we're token-authenticated + handler.get_current_user() + token_authenticated = getattr(handler, '_token_authenticated', False) + return not token_authenticated + @classmethod def get_user(cls, handler): """Called by handlers.get_current_user for identifying the current user. @@ -137,17 +149,23 @@ class LoginHandler(IPythonHandler): # check login token from URL argument or Authorization header user_token = cls.get_user_token(handler) one_time_token = handler.one_time_token + authenticated = False if user_token == token: # token-authenticated, set the login cookie handler.log.info("Accepting token-authenticated connection from %s", handler.request.remote_ip) - user_id = uuid.uuid4().hex - cls.set_login_cookie(handler, user_id) - if one_time_token and user_token == one_time_token: - # one-time token-authenticated, only allow this token once + authenticated = True + elif one_time_token and user_token == one_time_token: + # one-time-token-authenticated, only allow this token once handler.settings.pop('one_time_token', None) handler.log.info("Accepting one-time-token-authenticated connection from %s", handler.request.remote_ip) + authenticated = True + if authenticated: user_id = uuid.uuid4().hex cls.set_login_cookie(handler, user_id) + # Record that we've been authenticated with a token. + # Used in should_check_origin above. + handler._token_authenticated = True + # cache value for future retrievals on the same request handler._user_id = user_id return user_id diff --git a/notebook/base/handlers.py b/notebook/base/handlers.py index 1baceaeaf..dcabc4cb3 100755 --- a/notebook/base/handlers.py +++ b/notebook/base/handlers.py @@ -79,6 +79,16 @@ class AuthenticatedHandler(web.RequestHandler): return 'anonymous' return self.login_handler.get_user(self) + def skip_check_origin(self): + """Ask my login_handler if I should skip the origin_check + + For example: in the default LoginHandler, if a request is token-authenticated, + origin checking should be skipped. + """ + if self.login_handler is None or not hasattr(self.login_handler, 'should_check_origin'): + return False + return not self.login_handler.should_check_origin(self) + @property def cookie_name(self): default_cookie_name = non_alphanum.sub('-', 'username-{}'.format( @@ -267,8 +277,9 @@ class IPythonHandler(AuthenticatedHandler): Copied from WebSocket with changes: - allow unspecified host/origin (e.g. scripts) + - allow token-authenticated requests """ - if self.allow_origin == '*': + if self.allow_origin == '*' or self.skip_check_origin(): return True host = self.request.headers.get("Host") diff --git a/notebook/base/zmqhandlers.py b/notebook/base/zmqhandlers.py index 7c04159b3..66e31eb18 100644 --- a/notebook/base/zmqhandlers.py +++ b/notebook/base/zmqhandlers.py @@ -126,7 +126,9 @@ class WebSocketMixin(object): Tornado >= 4 calls this method automatically, raising 403 if it returns False. """ - if self.allow_origin == '*': + + if self.allow_origin == '*' or ( + hasattr(self, 'skip_check_origin') and self.skip_check_origin()): return True host = self.request.headers.get("Host")