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