Added authentication configuration for the notebook app.

This commit is contained in:
Phil Elson 2014-03-19 13:10:03 +00:00 committed by Min RK
parent a84f35373f
commit d81990c941
3 changed files with 52 additions and 32 deletions

View File

@ -3,10 +3,11 @@
Authors:
* Brian Granger
* Phil Elson
"""
#-----------------------------------------------------------------------------
# Copyright (C) 2011 The IPython Development Team
# Copyright (C) 2014 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
@ -19,7 +20,9 @@ Authors:
import uuid
from tornado.escape import url_escape
from tornado import web
from IPython.config.configurable import Configurable
from IPython.lib.security import passwd_check
from ..base.handlers import IPythonHandler
@ -29,7 +32,10 @@ from ..base.handlers import IPythonHandler
#-----------------------------------------------------------------------------
class LoginHandler(IPythonHandler):
""" The basic IPythonWebApplication login handler which authenticates with a
hashed password from the configuration.
"""
def _render(self, message=None):
self.write(self.render_template('login.html',
next=url_escape(self.get_argument('next', default=self.base_url)),
@ -43,20 +49,38 @@ class LoginHandler(IPythonHandler):
self._render()
def post(self):
pwd = self.get_argument('password', default=u'')
if self.login_available:
if passwd_check(self.password, pwd):
hashed_password = self.password_from_configuration(self.application)
typed_password = self.get_argument('password', default=u'')
if self.login_available(self.application):
if passwd_check(hashed_password, typed_password):
self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
else:
self._render(message={'error': 'Invalid password'})
return
self.redirect(self.get_argument('next', default=self.base_url))
@classmethod
def validate_notebook_app_security(cls, notebook_app, ssl_options=None):
if not notebook_app.ip:
warning = "WARNING: The notebook server is listening on all IP addresses"
if ssl_options is None:
notebook_app.log.critical(warning + " and not using encryption. This "
"is not recommended.")
if not self.password_from_configuration(notebook_app):
notebook_app.log.critical(warning + " and not using authentication. "
"This is highly insecure and not recommended.")
@staticmethod
def password_from_configuration(webapp):
""" Return the hashed password from the given NotebookWebApplication's configuration.
If there is no configured password, None will be returned.
"""
return webapp.settings['config']['NotebookApp'].get('password', None)
#-----------------------------------------------------------------------------
# URL to handler mappings
#-----------------------------------------------------------------------------
default_handlers = [(r"/login", LoginHandler)]
@classmethod
def login_available(cls, webapp):
"""Whether this LoginHandler is needed - and therefore whether the login page should be displayed."""
return bool(cls.password_from_configuration(webapp))

View File

@ -86,11 +86,6 @@ class AuthenticatedHandler(web.RequestHandler):
))
return self.settings.get('cookie_name', default_cookie_name)
@property
def password(self):
"""our password"""
return self.settings.get('password', '')
@property
def logged_in(self):
"""Is a user currently logged in?
@ -99,6 +94,11 @@ class AuthenticatedHandler(web.RequestHandler):
user = self.get_current_user()
return (user and not user == 'anonymous')
@property
def _login_handler(self):
"""Return the login handler for this application."""
return self.settings['login_handler_class']
@property
def login_available(self):
"""May a user proceed to log in?
@ -107,7 +107,7 @@ class AuthenticatedHandler(web.RequestHandler):
whether the user is already logged in or not.
"""
return bool(self.settings.get('password', ''))
return bool(self._login_handler.login_available(self.application))
class IPythonHandler(AuthenticatedHandler):

View File

@ -182,8 +182,9 @@ class NotebookWebApplication(web.Application):
# authentication
cookie_secret=ipython_app.cookie_secret,
login_url=url_path_join(base_url,'/login'),
login_handler_class=ipython_app.login_handler_class,
password=ipython_app.password,
# managers
kernel_manager=kernel_manager,
contents_manager=contents_manager,
@ -193,7 +194,7 @@ class NotebookWebApplication(web.Application):
config_manager=config_manager,
# IPython stuff
nbextensions_path = ipython_app.nbextensions_path,
nbextensions_path=ipython_app.nbextensions_path,
websocket_url=ipython_app.websocket_url,
mathjax_url=ipython_app.mathjax_url,
config=ipython_app.config,
@ -211,7 +212,7 @@ class NotebookWebApplication(web.Application):
# Order matters. The first handler to match the URL will handle the request.
handlers = []
handlers.extend(load_handlers('tree.handlers'))
handlers.extend(load_handlers('auth.login'))
handlers.extend([(r"/login", settings['login_handler_class'])])
handlers.extend(load_handlers('auth.logout'))
handlers.extend(load_handlers('files.handlers'))
handlers.extend(load_handlers('notebook.handlers'))
@ -501,7 +502,6 @@ class NotebookApp(BaseIPythonApplication):
jinja_environment_options = Dict(config=True,
help="Supply extra arguments that will be passed to Jinja environment.")
enable_mathjax = Bool(True, config=True,
help="""Whether to enable MathJax for typesetting math/TeX
@ -639,7 +639,6 @@ class NotebookApp(BaseIPythonApplication):
def _kernel_spec_manager_default(self):
return KernelSpecManager(ipython_dir=self.ipython_dir)
kernel_spec_manager_class = DottedObjectName('IPython.kernel.kernelspec.KernelSpecManager',
config=True,
help="""
@ -650,6 +649,10 @@ class NotebookApp(BaseIPythonApplication):
without warning between this version of IPython and the next stable one.
""")
login_handler = DottedObjectName('IPython.html.auth.login.LoginHandler',
config=True,
help='The login handler class to use.')
trust_xheaders = Bool(False, config=True,
help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
"sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
@ -700,7 +703,6 @@ class NotebookApp(BaseIPythonApplication):
# setting App.notebook_dir implies setting notebook and kernel dirs as well
self.config.FileContentsManager.root_dir = new
self.config.MappingKernelManager.root_dir = new
def parse_command_line(self, argv=None):
super(NotebookApp, self).parse_command_line(argv)
@ -748,6 +750,7 @@ class NotebookApp(BaseIPythonApplication):
kls = import_item(self.cluster_manager_class)
self.cluster_manager = kls(parent=self, log=self.log)
self.cluster_manager.update_profiles()
self.login_handler_class = import_item(self.login_handler)
kls = import_item(self.config_manager_class)
self.config_manager = kls(parent=self, log=self.log,
@ -788,17 +791,10 @@ class NotebookApp(BaseIPythonApplication):
ssl_options['keyfile'] = self.keyfile
else:
ssl_options = None
self.web_app.password = self.password
self.login_handler_class.validate_notebook_app_security(self, ssl_options=ssl_options)
self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
xheaders=self.trust_xheaders)
if not self.ip:
warning = "WARNING: The notebook server is listening on all IP addresses"
if ssl_options is None:
self.log.critical(warning + " and not using encryption. This "
"is not recommended.")
if not self.password:
self.log.critical(warning + " and not using authentication. "
"This is highly insecure and not recommended.")
success = None
for port in random_ports(self.port, self.port_retries+1):
try: