##// END OF EJS Templates
Merge pull request #6977 from minrk/finish-5384...
Matthias Bussonnier -
r19370:f4584e17 merge
parent child Browse files
Show More
@@ -1,20 +1,7 b''
1 """Tornado handlers logging into the notebook.
1 """Tornado handlers for logging into the notebook."""
2
2
3 Authors:
3 # Copyright (c) IPython Development Team.
4
4 # Distributed under the terms of the Modified BSD License.
5 * Brian Granger
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
5
19 import uuid
6 import uuid
20
7
@@ -24,12 +11,12 b' from IPython.lib.security import passwd_check'
24
11
25 from ..base.handlers import IPythonHandler
12 from ..base.handlers import IPythonHandler
26
13
27 #-----------------------------------------------------------------------------
28 # Handler
29 #-----------------------------------------------------------------------------
30
14
31 class LoginHandler(IPythonHandler):
15 class LoginHandler(IPythonHandler):
32
16 """The basic tornado login handler
17
18 authenticates with a hashed password from the configuration.
19 """
33 def _render(self, message=None):
20 def _render(self, message=None):
34 self.write(self.render_template('login.html',
21 self.write(self.render_template('login.html',
35 next=url_escape(self.get_argument('next', default=self.base_url)),
22 next=url_escape(self.get_argument('next', default=self.base_url)),
@@ -41,22 +28,68 b' class LoginHandler(IPythonHandler):'
41 self.redirect(self.get_argument('next', default=self.base_url))
28 self.redirect(self.get_argument('next', default=self.base_url))
42 else:
29 else:
43 self._render()
30 self._render()
31
32 @property
33 def hashed_password(self):
34 return self.password_from_settings(self.settings)
44
35
45 def post(self):
36 def post(self):
46 pwd = self.get_argument('password', default=u'')
37 typed_password = self.get_argument('password', default=u'')
47 if self.login_available:
38 if self.login_available(self.settings):
48 if passwd_check(self.password, pwd):
39 if passwd_check(self.hashed_password, typed_password):
49 self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
40 self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
50 else:
41 else:
51 self._render(message={'error': 'Invalid password'})
42 self._render(message={'error': 'Invalid password'})
52 return
43 return
53
44
54 self.redirect(self.get_argument('next', default=self.base_url))
45 self.redirect(self.get_argument('next', default=self.base_url))
46
47 @classmethod
48 def get_user(cls, handler):
49 """Called by handlers.get_current_user for identifying the current user.
50
51 See tornado.web.RequestHandler.get_current_user for details.
52 """
53 # Can't call this get_current_user because it will collide when
54 # called on LoginHandler itself.
55
56 user_id = handler.get_secure_cookie(handler.cookie_name)
57 # For now the user_id should not return empty, but it could, eventually.
58 if user_id == '':
59 user_id = 'anonymous'
60 if user_id is None:
61 # prevent extra Invalid cookie sig warnings:
62 handler.clear_login_cookie()
63 if not handler.login_available:
64 user_id = 'anonymous'
65 return user_id
66
67
68 @classmethod
69 def validate_security(cls, app, ssl_options=None):
70 """Check the notebook application's security.
71
72 Show messages, or abort if necessary, based on the security configuration.
73 """
74 if not app.ip:
75 warning = "WARNING: The notebook server is listening on all IP addresses"
76 if ssl_options is None:
77 app.log.critical(warning + " and not using encryption. This "
78 "is not recommended.")
79 if not app.password:
80 app.log.critical(warning + " and not using authentication. "
81 "This is highly insecure and not recommended.")
82
83 @classmethod
84 def password_from_settings(cls, settings):
85 """Return the hashed password from the tornado settings.
86
87 If there is no configured password, an empty string will be returned.
88 """
89 return settings.get('password', u'')
90
91 @classmethod
92 def login_available(cls, settings):
93 """Whether this LoginHandler is needed - and therefore whether the login page should be displayed."""
94 return bool(cls.password_from_settings(settings))
55
95
56
57 #-----------------------------------------------------------------------------
58 # URL to handler mappings
59 #-----------------------------------------------------------------------------
60
61
62 default_handlers = [(r"/login", LoginHandler)]
@@ -68,16 +68,9 b' class AuthenticatedHandler(web.RequestHandler):'
68 self.clear_cookie(self.cookie_name)
68 self.clear_cookie(self.cookie_name)
69
69
70 def get_current_user(self):
70 def get_current_user(self):
71 user_id = self.get_secure_cookie(self.cookie_name)
71 if self.login_handler is None:
72 # For now the user_id should not return empty, but it could eventually
72 return 'anonymous'
73 if user_id == '':
73 return self.login_handler.get_user(self)
74 user_id = 'anonymous'
75 if user_id is None:
76 # prevent extra Invalid cookie sig warnings:
77 self.clear_login_cookie()
78 if not self.login_available:
79 user_id = 'anonymous'
80 return user_id
81
74
82 @property
75 @property
83 def cookie_name(self):
76 def cookie_name(self):
@@ -87,19 +80,17 b' class AuthenticatedHandler(web.RequestHandler):'
87 return self.settings.get('cookie_name', default_cookie_name)
80 return self.settings.get('cookie_name', default_cookie_name)
88
81
89 @property
82 @property
90 def password(self):
91 """our password"""
92 return self.settings.get('password', '')
93
94 @property
95 def logged_in(self):
83 def logged_in(self):
96 """Is a user currently logged in?
84 """Is a user currently logged in?"""
97
98 """
99 user = self.get_current_user()
85 user = self.get_current_user()
100 return (user and not user == 'anonymous')
86 return (user and not user == 'anonymous')
101
87
102 @property
88 @property
89 def login_handler(self):
90 """Return the login handler for this application, if any."""
91 return self.settings.get('login_handler_class', None)
92
93 @property
103 def login_available(self):
94 def login_available(self):
104 """May a user proceed to log in?
95 """May a user proceed to log in?
105
96
@@ -107,7 +98,9 b' class AuthenticatedHandler(web.RequestHandler):'
107 whether the user is already logged in or not.
98 whether the user is already logged in or not.
108
99
109 """
100 """
110 return bool(self.settings.get('password', ''))
101 if self.login_handler is None:
102 return False
103 return bool(self.login_handler.login_available(self.settings))
111
104
112
105
113 class IPythonHandler(AuthenticatedHandler):
106 class IPythonHandler(AuthenticatedHandler):
@@ -182,8 +182,10 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,
186 logout_handler_class=ipython_app.logout_handler_class,
185 password=ipython_app.password,
187 password=ipython_app.password,
186
188
187 # managers
189 # managers
188 kernel_manager=kernel_manager,
190 kernel_manager=kernel_manager,
189 contents_manager=contents_manager,
191 contents_manager=contents_manager,
@@ -193,7 +195,7 b' class NotebookWebApplication(web.Application):'
193 config_manager=config_manager,
195 config_manager=config_manager,
194
196
195 # IPython stuff
197 # IPython stuff
196 nbextensions_path = ipython_app.nbextensions_path,
198 nbextensions_path=ipython_app.nbextensions_path,
197 websocket_url=ipython_app.websocket_url,
199 websocket_url=ipython_app.websocket_url,
198 mathjax_url=ipython_app.mathjax_url,
200 mathjax_url=ipython_app.mathjax_url,
199 config=ipython_app.config,
201 config=ipython_app.config,
@@ -211,8 +213,8 b' class NotebookWebApplication(web.Application):'
211 # Order matters. The first handler to match the URL will handle the request.
213 # Order matters. The first handler to match the URL will handle the request.
212 handlers = []
214 handlers = []
213 handlers.extend(load_handlers('tree.handlers'))
215 handlers.extend(load_handlers('tree.handlers'))
214 handlers.extend(load_handlers('auth.login'))
216 handlers.extend([(r"/login", settings['login_handler_class'])])
215 handlers.extend(load_handlers('auth.logout'))
217 handlers.extend([(r"/logout", settings['logout_handler_class'])])
216 handlers.extend(load_handlers('files.handlers'))
218 handlers.extend(load_handlers('files.handlers'))
217 handlers.extend(load_handlers('notebook.handlers'))
219 handlers.extend(load_handlers('notebook.handlers'))
218 handlers.extend(load_handlers('nbconvert.handlers'))
220 handlers.extend(load_handlers('nbconvert.handlers'))
@@ -501,7 +503,6 b' class NotebookApp(BaseIPythonApplication):'
501
503
502 jinja_environment_options = Dict(config=True,
504 jinja_environment_options = Dict(config=True,
503 help="Supply extra arguments that will be passed to Jinja environment.")
505 help="Supply extra arguments that will be passed to Jinja environment.")
504
505
506
506 enable_mathjax = Bool(True, config=True,
507 enable_mathjax = Bool(True, config=True,
507 help="""Whether to enable MathJax for typesetting math/TeX
508 help="""Whether to enable MathJax for typesetting math/TeX
@@ -639,7 +640,6 b' class NotebookApp(BaseIPythonApplication):'
639 def _kernel_spec_manager_default(self):
640 def _kernel_spec_manager_default(self):
640 return KernelSpecManager(ipython_dir=self.ipython_dir)
641 return KernelSpecManager(ipython_dir=self.ipython_dir)
641
642
642
643 kernel_spec_manager_class = DottedObjectName('IPython.kernel.kernelspec.KernelSpecManager',
643 kernel_spec_manager_class = DottedObjectName('IPython.kernel.kernelspec.KernelSpecManager',
644 config=True,
644 config=True,
645 help="""
645 help="""
@@ -650,6 +650,14 b' class NotebookApp(BaseIPythonApplication):'
650 without warning between this version of IPython and the next stable one.
650 without warning between this version of IPython and the next stable one.
651 """)
651 """)
652
652
653 login_handler = DottedObjectName('IPython.html.auth.login.LoginHandler',
654 config=True,
655 help='The login handler class to use.')
656
657 logout_handler = DottedObjectName('IPython.html.auth.logout.LogoutHandler',
658 config=True,
659 help='The logout handler class to use.')
660
653 trust_xheaders = Bool(False, config=True,
661 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"
662 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")
663 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
@@ -700,7 +708,6 b' class NotebookApp(BaseIPythonApplication):'
700 # setting App.notebook_dir implies setting notebook and kernel dirs as well
708 # setting App.notebook_dir implies setting notebook and kernel dirs as well
701 self.config.FileContentsManager.root_dir = new
709 self.config.FileContentsManager.root_dir = new
702 self.config.MappingKernelManager.root_dir = new
710 self.config.MappingKernelManager.root_dir = new
703
704
711
705 def parse_command_line(self, argv=None):
712 def parse_command_line(self, argv=None):
706 super(NotebookApp, self).parse_command_line(argv)
713 super(NotebookApp, self).parse_command_line(argv)
@@ -748,6 +755,8 b' class NotebookApp(BaseIPythonApplication):'
748 kls = import_item(self.cluster_manager_class)
755 kls = import_item(self.cluster_manager_class)
749 self.cluster_manager = kls(parent=self, log=self.log)
756 self.cluster_manager = kls(parent=self, log=self.log)
750 self.cluster_manager.update_profiles()
757 self.cluster_manager.update_profiles()
758 self.login_handler_class = import_item(self.login_handler)
759 self.logout_handler_class = import_item(self.logout_handler)
751
760
752 kls = import_item(self.config_manager_class)
761 kls = import_item(self.config_manager_class)
753 self.config_manager = kls(parent=self, log=self.log,
762 self.config_manager = kls(parent=self, log=self.log,
@@ -788,17 +797,10 b' class NotebookApp(BaseIPythonApplication):'
788 ssl_options['keyfile'] = self.keyfile
797 ssl_options['keyfile'] = self.keyfile
789 else:
798 else:
790 ssl_options = None
799 ssl_options = None
791 self.web_app.password = self.password
800 self.login_handler_class.validate_security(self, ssl_options=ssl_options)
792 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
801 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
793 xheaders=self.trust_xheaders)
802 xheaders=self.trust_xheaders)
794 if not self.ip:
803
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
804 success = None
803 for port in random_ports(self.port, self.port_retries+1):
805 for port in random_ports(self.port, self.port_retries+1):
804 try:
806 try:
General Comments 0
You need to be logged in to leave comments. Login now