diff --git a/IPython/html/auth/login.py b/IPython/html/auth/login.py
index 1ad467337..81fff81ef 100644
--- a/IPython/html/auth/login.py
+++ b/IPython/html/auth/login.py
@@ -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))
diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py
index fa58c2b25..19e53984c 100644
--- a/IPython/html/base/handlers.py
+++ b/IPython/html/base/handlers.py
@@ -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):
diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py
index d69b27411..e320819eb 100644
--- a/IPython/html/notebookapp.py
+++ b/IPython/html/notebookapp.py
@@ -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: