##// 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):
16 """The basic tornado login handler
32 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)),
@@ -42,10 +29,14 b' class LoginHandler(IPythonHandler):'
42 29 else:
43 30 self._render()
44 31
32 @property
33 def hashed_password(self):
34 return self.password_from_settings(self.settings)
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'})
@@ -53,10 +44,52 b' class LoginHandler(IPythonHandler):'
53 44
54 45 self.redirect(self.get_argument('next', default=self.base_url))
55 46
47 @classmethod
48 def get_user(cls, handler):
49 """Called by handlers.get_current_user for identifying the current user.
56 50
57 #-----------------------------------------------------------------------------
58 # URL to handler mappings
59 #-----------------------------------------------------------------------------
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'')
60 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))
61 95
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,6 +182,8 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
@@ -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'))
@@ -502,7 +504,6 b' class NotebookApp(BaseIPythonApplication):'
502 504 jinja_environment_options = Dict(config=True,
503 505 help="Supply extra arguments that will be passed to Jinja environment.")
504 506
505
506 507 enable_mathjax = Bool(True, config=True,
507 508 help="""Whether to enable MathJax for typesetting math/TeX
508 509
@@ -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")
@@ -701,7 +709,6 b' class NotebookApp(BaseIPythonApplication):'
701 709 self.config.FileContentsManager.root_dir = new
702 710 self.config.MappingKernelManager.root_dir = new
703 711
704
705 712 def parse_command_line(self, argv=None):
706 713 super(NotebookApp, self).parse_command_line(argv)
707 714
@@ -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