##// 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:
4
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 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
18 5
19 6 import uuid
20 7
@@ -24,12 +11,12 b' from IPython.lib.security import passwd_check'
24 11
25 12 from ..base.handlers import IPythonHandler
26 13
27 #-----------------------------------------------------------------------------
28 # Handler
29 #-----------------------------------------------------------------------------
30 14
31 15 class LoginHandler(IPythonHandler):
32
16 """The basic tornado login handler
17
18 authenticates with a hashed password from the configuration.
19 """
33 20 def _render(self, message=None):
34 21 self.write(self.render_template('login.html',
35 22 next=url_escape(self.get_argument('next', default=self.base_url)),
@@ -41,22 +28,68 b' class LoginHandler(IPythonHandler):'
41 28 self.redirect(self.get_argument('next', default=self.base_url))
42 29 else:
43 30 self._render()
31
32 @property
33 def hashed_password(self):
34 return self.password_from_settings(self.settings)
44 35
45 36 def post(self):
46 pwd = self.get_argument('password', default=u'')
47 if self.login_available:
48 if passwd_check(self.password, pwd):
37 typed_password = self.get_argument('password', default=u'')
38 if self.login_available(self.settings):
39 if passwd_check(self.hashed_password, typed_password):
49 40 self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
50 41 else:
51 42 self._render(message={'error': 'Invalid password'})
52 43 return
53 44
54 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 68 self.clear_cookie(self.cookie_name)
69 69
70 70 def get_current_user(self):
71 user_id = self.get_secure_cookie(self.cookie_name)
72 # For now the user_id should not return empty, but it could eventually
73 if user_id == '':
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
71 if self.login_handler is None:
72 return 'anonymous'
73 return self.login_handler.get_user(self)
81 74
82 75 @property
83 76 def cookie_name(self):
@@ -87,19 +80,17 b' class AuthenticatedHandler(web.RequestHandler):'
87 80 return self.settings.get('cookie_name', default_cookie_name)
88 81
89 82 @property
90 def password(self):
91 """our password"""
92 return self.settings.get('password', '')
93
94 @property
95 83 def logged_in(self):
96 """Is a user currently logged in?
97
98 """
84 """Is a user currently logged in?"""
99 85 user = self.get_current_user()
100 86 return (user and not user == 'anonymous')
101 87
102 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 94 def login_available(self):
104 95 """May a user proceed to log in?
105 96
@@ -107,7 +98,9 b' class AuthenticatedHandler(web.RequestHandler):'
107 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 106 class IPythonHandler(AuthenticatedHandler):
@@ -182,8 +182,10 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,
186 logout_handler_class=ipython_app.logout_handler_class,
185 187 password=ipython_app.password,
186
188
187 189 # managers
188 190 kernel_manager=kernel_manager,
189 191 contents_manager=contents_manager,
@@ -193,7 +195,7 b' class NotebookWebApplication(web.Application):'
193 195 config_manager=config_manager,
194 196
195 197 # IPython stuff
196 nbextensions_path = ipython_app.nbextensions_path,
198 nbextensions_path=ipython_app.nbextensions_path,
197 199 websocket_url=ipython_app.websocket_url,
198 200 mathjax_url=ipython_app.mathjax_url,
199 201 config=ipython_app.config,
@@ -211,8 +213,8 b' class NotebookWebApplication(web.Application):'
211 213 # Order matters. The first handler to match the URL will handle the request.
212 214 handlers = []
213 215 handlers.extend(load_handlers('tree.handlers'))
214 handlers.extend(load_handlers('auth.login'))
215 handlers.extend(load_handlers('auth.logout'))
216 handlers.extend([(r"/login", settings['login_handler_class'])])
217 handlers.extend([(r"/logout", settings['logout_handler_class'])])
216 218 handlers.extend(load_handlers('files.handlers'))
217 219 handlers.extend(load_handlers('notebook.handlers'))
218 220 handlers.extend(load_handlers('nbconvert.handlers'))
@@ -501,7 +503,6 b' class NotebookApp(BaseIPythonApplication):'
501 503
502 504 jinja_environment_options = Dict(config=True,
503 505 help="Supply extra arguments that will be passed to Jinja environment.")
504
505 506
506 507 enable_mathjax = Bool(True, config=True,
507 508 help="""Whether to enable MathJax for typesetting math/TeX
@@ -639,7 +640,6 b' class NotebookApp(BaseIPythonApplication):'
639 640 def _kernel_spec_manager_default(self):
640 641 return KernelSpecManager(ipython_dir=self.ipython_dir)
641 642
642
643 643 kernel_spec_manager_class = DottedObjectName('IPython.kernel.kernelspec.KernelSpecManager',
644 644 config=True,
645 645 help="""
@@ -650,6 +650,14 b' class NotebookApp(BaseIPythonApplication):'
650 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 661 trust_xheaders = Bool(False, config=True,
654 662 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
655 663 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
@@ -700,7 +708,6 b' class NotebookApp(BaseIPythonApplication):'
700 708 # setting App.notebook_dir implies setting notebook and kernel dirs as well
701 709 self.config.FileContentsManager.root_dir = new
702 710 self.config.MappingKernelManager.root_dir = new
703
704 711
705 712 def parse_command_line(self, argv=None):
706 713 super(NotebookApp, self).parse_command_line(argv)
@@ -748,6 +755,8 b' class NotebookApp(BaseIPythonApplication):'
748 755 kls = import_item(self.cluster_manager_class)
749 756 self.cluster_manager = kls(parent=self, log=self.log)
750 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 761 kls = import_item(self.config_manager_class)
753 762 self.config_manager = kls(parent=self, log=self.log,
@@ -788,17 +797,10 b' class NotebookApp(BaseIPythonApplication):'
788 797 ssl_options['keyfile'] = self.keyfile
789 798 else:
790 799 ssl_options = None
791 self.web_app.password = self.password
800 self.login_handler_class.validate_security(self, ssl_options=ssl_options)
792 801 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
793 802 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.")
803
802 804 success = None
803 805 for port in random_ports(self.port, self.port_retries+1):
804 806 try:
General Comments 0
You need to be logged in to leave comments. Login now