##// END OF EJS Templates
Added authentication configuration for the notebook app.
Phil Elson -
Show More
@@ -3,10 +3,11 b''
3 3 Authors:
4 4
5 5 * Brian Granger
6 * Phil Elson
6 7 """
7 8
8 9 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
10 # Copyright (C) 2014 The IPython Development Team
10 11 #
11 12 # Distributed under the terms of the BSD License. The full license is in
12 13 # the file COPYING, distributed as part of this software.
@@ -19,7 +20,9 b' Authors:'
19 20 import uuid
20 21
21 22 from tornado.escape import url_escape
23 from tornado import web
22 24
25 from IPython.config.configurable import Configurable
23 26 from IPython.lib.security import passwd_check
24 27
25 28 from ..base.handlers import IPythonHandler
@@ -29,7 +32,10 b' from ..base.handlers import IPythonHandler'
29 32 #-----------------------------------------------------------------------------
30 33
31 34 class LoginHandler(IPythonHandler):
32
35 """ The basic IPythonWebApplication login handler which authenticates with a
36 hashed password from the configuration.
37
38 """
33 39 def _render(self, message=None):
34 40 self.write(self.render_template('login.html',
35 41 next=url_escape(self.get_argument('next', default=self.base_url)),
@@ -43,20 +49,38 b' class LoginHandler(IPythonHandler):'
43 49 self._render()
44 50
45 51 def post(self):
46 pwd = self.get_argument('password', default=u'')
47 if self.login_available:
48 if passwd_check(self.password, pwd):
52 hashed_password = self.password_from_configuration(self.application)
53 typed_password = self.get_argument('password', default=u'')
54 if self.login_available(self.application):
55 if passwd_check(hashed_password, typed_password):
49 56 self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
50 57 else:
51 58 self._render(message={'error': 'Invalid password'})
52 59 return
53 60
54 61 self.redirect(self.get_argument('next', default=self.base_url))
55
56
57 #-----------------------------------------------------------------------------
58 # URL to handler mappings
59 #-----------------------------------------------------------------------------
60
61
62 default_handlers = [(r"/login", LoginHandler)]
62
63 @classmethod
64 def validate_notebook_app_security(cls, notebook_app, ssl_options=None):
65 if not notebook_app.ip:
66 warning = "WARNING: The notebook server is listening on all IP addresses"
67 if ssl_options is None:
68 notebook_app.log.critical(warning + " and not using encryption. This "
69 "is not recommended.")
70 if not self.password_from_configuration(notebook_app):
71 notebook_app.log.critical(warning + " and not using authentication. "
72 "This is highly insecure and not recommended.")
73
74 @staticmethod
75 def password_from_configuration(webapp):
76 """ Return the hashed password from the given NotebookWebApplication's configuration.
77
78 If there is no configured password, None will be returned.
79
80 """
81 return webapp.settings['config']['NotebookApp'].get('password', None)
82
83 @classmethod
84 def login_available(cls, webapp):
85 """Whether this LoginHandler is needed - and therefore whether the login page should be displayed."""
86 return bool(cls.password_from_configuration(webapp))
@@ -87,11 +87,6 b' class AuthenticatedHandler(web.RequestHandler):'
87 87 return self.settings.get('cookie_name', default_cookie_name)
88 88
89 89 @property
90 def password(self):
91 """our password"""
92 return self.settings.get('password', '')
93
94 @property
95 90 def logged_in(self):
96 91 """Is a user currently logged in?
97 92
@@ -100,6 +95,11 b' class AuthenticatedHandler(web.RequestHandler):'
100 95 return (user and not user == 'anonymous')
101 96
102 97 @property
98 def _login_handler(self):
99 """Return the login handler for this application."""
100 return self.settings['login_handler_class']
101
102 @property
103 103 def login_available(self):
104 104 """May a user proceed to log in?
105 105
@@ -107,7 +107,7 b' class AuthenticatedHandler(web.RequestHandler):'
107 107 whether the user is already logged in or not.
108 108
109 109 """
110 return bool(self.settings.get('password', ''))
110 return bool(self._login_handler.login_available(self.application))
111 111
112 112
113 113 class IPythonHandler(AuthenticatedHandler):
@@ -182,8 +182,9 b' class NotebookWebApplication(web.Application):'
182 182 # authentication
183 183 cookie_secret=ipython_app.cookie_secret,
184 184 login_url=url_path_join(base_url,'/login'),
185 login_handler_class=ipython_app.login_handler_class,
185 186 password=ipython_app.password,
186
187
187 188 # managers
188 189 kernel_manager=kernel_manager,
189 190 contents_manager=contents_manager,
@@ -193,7 +194,7 b' class NotebookWebApplication(web.Application):'
193 194 config_manager=config_manager,
194 195
195 196 # IPython stuff
196 nbextensions_path = ipython_app.nbextensions_path,
197 nbextensions_path=ipython_app.nbextensions_path,
197 198 websocket_url=ipython_app.websocket_url,
198 199 mathjax_url=ipython_app.mathjax_url,
199 200 config=ipython_app.config,
@@ -211,7 +212,7 b' class NotebookWebApplication(web.Application):'
211 212 # Order matters. The first handler to match the URL will handle the request.
212 213 handlers = []
213 214 handlers.extend(load_handlers('tree.handlers'))
214 handlers.extend(load_handlers('auth.login'))
215 handlers.extend([(r"/login", settings['login_handler_class'])])
215 216 handlers.extend(load_handlers('auth.logout'))
216 217 handlers.extend(load_handlers('files.handlers'))
217 218 handlers.extend(load_handlers('notebook.handlers'))
@@ -501,7 +502,6 b' class NotebookApp(BaseIPythonApplication):'
501 502
502 503 jinja_environment_options = Dict(config=True,
503 504 help="Supply extra arguments that will be passed to Jinja environment.")
504
505 505
506 506 enable_mathjax = Bool(True, config=True,
507 507 help="""Whether to enable MathJax for typesetting math/TeX
@@ -639,7 +639,6 b' class NotebookApp(BaseIPythonApplication):'
639 639 def _kernel_spec_manager_default(self):
640 640 return KernelSpecManager(ipython_dir=self.ipython_dir)
641 641
642
643 642 kernel_spec_manager_class = DottedObjectName('IPython.kernel.kernelspec.KernelSpecManager',
644 643 config=True,
645 644 help="""
@@ -650,6 +649,10 b' class NotebookApp(BaseIPythonApplication):'
650 649 without warning between this version of IPython and the next stable one.
651 650 """)
652 651
652 login_handler = DottedObjectName('IPython.html.auth.login.LoginHandler',
653 config=True,
654 help='The login handler class to use.')
655
653 656 trust_xheaders = Bool(False, config=True,
654 657 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
655 658 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
@@ -700,7 +703,6 b' class NotebookApp(BaseIPythonApplication):'
700 703 # setting App.notebook_dir implies setting notebook and kernel dirs as well
701 704 self.config.FileContentsManager.root_dir = new
702 705 self.config.MappingKernelManager.root_dir = new
703
704 706
705 707 def parse_command_line(self, argv=None):
706 708 super(NotebookApp, self).parse_command_line(argv)
@@ -748,6 +750,7 b' class NotebookApp(BaseIPythonApplication):'
748 750 kls = import_item(self.cluster_manager_class)
749 751 self.cluster_manager = kls(parent=self, log=self.log)
750 752 self.cluster_manager.update_profiles()
753 self.login_handler_class = import_item(self.login_handler)
751 754
752 755 kls = import_item(self.config_manager_class)
753 756 self.config_manager = kls(parent=self, log=self.log,
@@ -788,17 +791,10 b' class NotebookApp(BaseIPythonApplication):'
788 791 ssl_options['keyfile'] = self.keyfile
789 792 else:
790 793 ssl_options = None
791 self.web_app.password = self.password
794 self.login_handler_class.validate_notebook_app_security(self, ssl_options=ssl_options)
792 795 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
793 796 xheaders=self.trust_xheaders)
794 if not self.ip:
795 warning = "WARNING: The notebook server is listening on all IP addresses"
796 if ssl_options is None:
797 self.log.critical(warning + " and not using encryption. This "
798 "is not recommended.")
799 if not self.password:
800 self.log.critical(warning + " and not using authentication. "
801 "This is highly insecure and not recommended.")
797
802 798 success = None
803 799 for port in random_ports(self.port, self.port_retries+1):
804 800 try:
General Comments 0
You need to be logged in to leave comments. Login now