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) 201 |
|
|
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 |
|
|
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( |
|
|
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