|
|
|
|
@ -3,6 +3,8 @@
|
|
|
|
|
# Copyright (c) Jupyter Development Team.
|
|
|
|
|
# Distributed under the terms of the Modified BSD License.
|
|
|
|
|
|
|
|
|
|
import re
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
from urllib.parse import urlparse # Py 3
|
|
|
|
|
except ImportError:
|
|
|
|
|
@ -67,15 +69,11 @@ class LoginHandler(IPythonHandler):
|
|
|
|
|
|
|
|
|
|
def post(self):
|
|
|
|
|
typed_password = self.get_argument('password', default=u'')
|
|
|
|
|
cookie_options = self.settings.get('cookie_options', {})
|
|
|
|
|
cookie_options.setdefault('httponly', True)
|
|
|
|
|
if self.login_available(self.settings):
|
|
|
|
|
if self.get_login_available(self.settings):
|
|
|
|
|
if passwd_check(self.hashed_password, typed_password):
|
|
|
|
|
# tornado <4.2 has a bug that considers secure==True as soon as
|
|
|
|
|
# 'secure' kwarg is passed to set_secure_cookie
|
|
|
|
|
if self.settings.get('secure_cookie', self.request.protocol == 'https'):
|
|
|
|
|
cookie_options.setdefault('secure', True)
|
|
|
|
|
self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()), **cookie_options)
|
|
|
|
|
self.set_login_cookie(self, uuid.uuid4().hex)
|
|
|
|
|
elif self.token and self.token == typed_password:
|
|
|
|
|
self.set_login_cookie(self, uuid.uuid4().hex)
|
|
|
|
|
else:
|
|
|
|
|
self.set_status(401)
|
|
|
|
|
self._render(message={'error': 'Invalid password'})
|
|
|
|
|
@ -84,6 +82,38 @@ class LoginHandler(IPythonHandler):
|
|
|
|
|
next_url = self.get_argument('next', default=self.base_url)
|
|
|
|
|
self._redirect_safe(next_url)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def set_login_cookie(cls, handler, user_id=None):
|
|
|
|
|
"""Call this on handlers to set the login cookie for success"""
|
|
|
|
|
cookie_options = handler.settings.get('cookie_options', {})
|
|
|
|
|
cookie_options.setdefault('httponly', True)
|
|
|
|
|
# tornado <4.2 has a bug that considers secure==True as soon as
|
|
|
|
|
# 'secure' kwarg is passed to set_secure_cookie
|
|
|
|
|
if handler.settings.get('secure_cookie', handler.request.protocol == 'https'):
|
|
|
|
|
cookie_options.setdefault('secure', True)
|
|
|
|
|
handler.set_secure_cookie(handler.cookie_name, user_id, **cookie_options)
|
|
|
|
|
return user_id
|
|
|
|
|
|
|
|
|
|
auth_header_pat = re.compile('token\s+(.+)', re.IGNORECASE)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def get_user_token(cls, handler):
|
|
|
|
|
"""Get the user token from a request
|
|
|
|
|
|
|
|
|
|
Default:
|
|
|
|
|
|
|
|
|
|
- in URL parameters: ?token=<token>
|
|
|
|
|
- in header: Authorization: token <token>
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
user_token = handler.get_argument('token', '')
|
|
|
|
|
if not user_token:
|
|
|
|
|
# get it from Authorization header
|
|
|
|
|
m = cls.auth_header_pat.match(handler.request.headers.get('Authorization', ''))
|
|
|
|
|
if m:
|
|
|
|
|
user_token = m.group(1)
|
|
|
|
|
return user_token
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def get_user(cls, handler):
|
|
|
|
|
"""Called by handlers.get_current_user for identifying the current user.
|
|
|
|
|
@ -92,16 +122,34 @@ class LoginHandler(IPythonHandler):
|
|
|
|
|
"""
|
|
|
|
|
# Can't call this get_current_user because it will collide when
|
|
|
|
|
# called on LoginHandler itself.
|
|
|
|
|
|
|
|
|
|
if getattr(handler, '_user_id', None):
|
|
|
|
|
return handler._user_id
|
|
|
|
|
user_id = handler.get_secure_cookie(handler.cookie_name)
|
|
|
|
|
# For now the user_id should not return empty, but it could, eventually.
|
|
|
|
|
if user_id == '':
|
|
|
|
|
user_id = 'anonymous'
|
|
|
|
|
if user_id is None:
|
|
|
|
|
if not user_id:
|
|
|
|
|
# prevent extra Invalid cookie sig warnings:
|
|
|
|
|
handler.clear_login_cookie()
|
|
|
|
|
if not handler.login_available:
|
|
|
|
|
user_id = 'anonymous'
|
|
|
|
|
token = handler.token
|
|
|
|
|
if not token and not handler.login_available:
|
|
|
|
|
# Completely insecure! No authentication at all.
|
|
|
|
|
# No need to warn here, though; validate_security will have already done that.
|
|
|
|
|
return 'anonymous'
|
|
|
|
|
if token:
|
|
|
|
|
# check login token from URL argument or Authorization header
|
|
|
|
|
user_token = cls.get_user_token(handler)
|
|
|
|
|
one_time_token = handler.one_time_token
|
|
|
|
|
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
|
|
|
|
|
handler.settings.pop('one_time_token', None)
|
|
|
|
|
handler.log.info("Accepting one-time-token-authenticated connection from %s", handler.request.remote_ip)
|
|
|
|
|
user_id = uuid.uuid4().hex
|
|
|
|
|
cls.set_login_cookie(handler, user_id)
|
|
|
|
|
# cache value for future retrievals on the same request
|
|
|
|
|
handler._user_id = user_id
|
|
|
|
|
return user_id
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -116,9 +164,14 @@ class LoginHandler(IPythonHandler):
|
|
|
|
|
if ssl_options is None:
|
|
|
|
|
app.log.warning(warning + " and not using encryption. This "
|
|
|
|
|
"is not recommended.")
|
|
|
|
|
if not app.password:
|
|
|
|
|
if not app.password and not app.token:
|
|
|
|
|
app.log.warning(warning + " and not using authentication. "
|
|
|
|
|
"This is highly insecure and not recommended.")
|
|
|
|
|
else:
|
|
|
|
|
if not app.password and not app.token:
|
|
|
|
|
app.log.warning(
|
|
|
|
|
"All authentication is disabled."
|
|
|
|
|
" Anyone who can connect to this server will be able to run code.")
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def password_from_settings(cls, settings):
|
|
|
|
|
@ -129,6 +182,6 @@ class LoginHandler(IPythonHandler):
|
|
|
|
|
return settings.get('password', u'')
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def login_available(cls, settings):
|
|
|
|
|
def get_login_available(cls, settings):
|
|
|
|
|
"""Whether this LoginHandler is needed - and therefore whether the login page should be displayed."""
|
|
|
|
|
return bool(cls.password_from_settings(settings))
|
|
|
|
|
return bool(cls.password_from_settings(settings) or settings.get('token'))
|
|
|
|
|
|