login.py
109 lines
| 4.0 KiB
| text/x-python
|
PythonLexer
|
r19323 | """Tornado handlers for logging into the notebook.""" | ||
|
r10641 | |||
|
r19323 | # Copyright (c) IPython Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||||
|
r10641 | |||
import uuid | ||||
from tornado.escape import url_escape | ||||
from IPython.lib.security import passwd_check | ||||
|
r10649 | from ..base.handlers import IPythonHandler | ||
|
r10641 | |||
class LoginHandler(IPythonHandler): | ||||
|
r19323 | """The basic tornado login handler | ||
|
r19322 | |||
|
r19323 | authenticates with a hashed password from the configuration. | ||
|
r19322 | """ | ||
|
r10641 | def _render(self, message=None): | ||
self.write(self.render_template('login.html', | ||||
|
r15238 | next=url_escape(self.get_argument('next', default=self.base_url)), | ||
|
r10641 | message=message, | ||
)) | ||||
def get(self): | ||||
if self.current_user: | ||||
|
r21489 | next_url = self.get_argument('next', default=self.base_url) | ||
if not next_url.startswith(self.base_url): | ||||
# require that next_url be absolute path within our path | ||||
next_url = self.base_url | ||||
self.redirect(next_url) | ||||
|
r10641 | else: | ||
self._render() | ||||
|
r19323 | |||
@property | ||||
def hashed_password(self): | ||||
return self.password_from_settings(self.settings) | ||||
|
r10641 | |||
def post(self): | ||||
|
r19322 | typed_password = self.get_argument('password', default=u'') | ||
|
r19323 | if self.login_available(self.settings): | ||
if passwd_check(self.hashed_password, typed_password): | ||||
|
r21269 | # tornado <4.2 have a bug that consider secure==True as soon as | ||
# 'secure' kwarg is passed to set_secure_cookie | ||||
if self.settings.get('secure_cookie', self.request.protocol == 'https'): | ||||
kwargs = {'secure':True} | ||||
else: | ||||
kwargs = {} | ||||
self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()), **kwargs) | ||||
|
r10641 | else: | ||
self._render(message={'error': 'Invalid password'}) | ||||
return | ||||
|
r21489 | |||
next_url = self.get_argument('next', default=self.base_url) | ||||
if not next_url.startswith(self.base_url): | ||||
# require that next_url be absolute path within our path | ||||
next_url = self.base_url | ||||
self.redirect(next_url) | ||||
|
r19322 | |||
|
r19333 | @classmethod | ||
def get_user(cls, handler): | ||||
"""Called by handlers.get_current_user for identifying the current user. | ||||
See tornado.web.RequestHandler.get_current_user for details. | ||||
""" | ||||
|
r19325 | # Can't call this get_current_user because it will collide when | ||
# called on LoginHandler itself. | ||||
user_id = handler.get_secure_cookie(handler.cookie_name) | ||||
|
r19333 | # For now the user_id should not return empty, but it could, eventually. | ||
|
r19325 | if user_id == '': | ||
user_id = 'anonymous' | ||||
if user_id is None: | ||||
# prevent extra Invalid cookie sig warnings: | ||||
handler.clear_login_cookie() | ||||
if not handler.login_available: | ||||
user_id = 'anonymous' | ||||
return user_id | ||||
|
r19322 | @classmethod | ||
|
r19333 | def validate_security(cls, app, ssl_options=None): | ||
"""Check the notebook application's security. | ||||
Show messages, or abort if necessary, based on the security configuration. | ||||
""" | ||||
if not app.ip: | ||||
|
r19322 | warning = "WARNING: The notebook server is listening on all IP addresses" | ||
if ssl_options is None: | ||||
|
r20709 | app.log.warning(warning + " and not using encryption. This " | ||
|
r19322 | "is not recommended.") | ||
|
r19333 | if not app.password: | ||
|
r20709 | app.log.warning(warning + " and not using authentication. " | ||
|
r19322 | "This is highly insecure and not recommended.") | ||
|
r19333 | @classmethod | ||
def password_from_settings(cls, settings): | ||||
|
r19323 | """Return the hashed password from the tornado settings. | ||
|
r19322 | |||
|
r19323 | If there is no configured password, an empty string will be returned. | ||
|
r19322 | """ | ||
|
r19323 | return settings.get('password', u'') | ||
|
r19322 | |||
@classmethod | ||||
|
r19323 | def login_available(cls, settings): | ||
|
r19322 | """Whether this LoginHandler is needed - and therefore whether the login page should be displayed.""" | ||
|
r19323 | return bool(cls.password_from_settings(settings)) | ||