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