Uploaded Test files
This commit is contained in:
parent
f584ad9d97
commit
2e81cb7d99
16627 changed files with 2065359 additions and 102444 deletions
1
venv/Lib/site-packages/notebook/auth/__init__.py
Normal file
1
venv/Lib/site-packages/notebook/auth/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from .security import passwd
|
42
venv/Lib/site-packages/notebook/auth/__main__.py
Normal file
42
venv/Lib/site-packages/notebook/auth/__main__.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
from notebook.auth import passwd
|
||||
from getpass import getpass
|
||||
from notebook.config_manager import BaseJSONConfigManager
|
||||
from jupyter_core.paths import jupyter_config_dir
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
def set_password(args):
|
||||
password = args.password
|
||||
while not password :
|
||||
password1 = getpass("" if args.quiet else "Provide password: ")
|
||||
password_repeat = getpass("" if args.quiet else "Repeat password: ")
|
||||
if password1 != password_repeat:
|
||||
print("Passwords do not match, try again")
|
||||
elif len(password1) < 4:
|
||||
print("Please provide at least 4 characters")
|
||||
else:
|
||||
password = password1
|
||||
|
||||
password_hash = passwd(password)
|
||||
cfg = BaseJSONConfigManager(config_dir=jupyter_config_dir())
|
||||
cfg.update('jupyter_notebook_config', {
|
||||
'NotebookApp': {
|
||||
'password': password_hash,
|
||||
}
|
||||
})
|
||||
if not args.quiet:
|
||||
print("password stored in config dir: %s" % jupyter_config_dir())
|
||||
|
||||
def main(argv):
|
||||
parser = argparse.ArgumentParser(argv[0])
|
||||
subparsers = parser.add_subparsers()
|
||||
parser_password = subparsers.add_parser('password', help='sets a password for your notebook server')
|
||||
parser_password.add_argument("password", help="password to set, if not given, a password will be queried for (NOTE: this may not be safe)",
|
||||
nargs="?")
|
||||
parser_password.add_argument("--quiet", help="suppress messages", action="store_true")
|
||||
parser_password.set_defaults(function=set_password)
|
||||
args = parser.parse_args(argv[1:])
|
||||
args.function(args)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
253
venv/Lib/site-packages/notebook/auth/login.py
Normal file
253
venv/Lib/site-packages/notebook/auth/login.py
Normal file
|
@ -0,0 +1,253 @@
|
|||
"""Tornado handlers for logging into the notebook."""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import re
|
||||
import os
|
||||
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import uuid
|
||||
|
||||
from tornado.escape import url_escape
|
||||
|
||||
from .security import passwd_check, set_password
|
||||
|
||||
from ..base.handlers import IPythonHandler
|
||||
|
||||
|
||||
class LoginHandler(IPythonHandler):
|
||||
"""The basic tornado login handler
|
||||
|
||||
authenticates with a hashed password from the configuration.
|
||||
"""
|
||||
def _render(self, message=None):
|
||||
self.write(self.render_template('login.html',
|
||||
next=url_escape(self.get_argument('next', default=self.base_url)),
|
||||
message=message,
|
||||
))
|
||||
|
||||
def _redirect_safe(self, url, default=None):
|
||||
"""Redirect if url is on our PATH
|
||||
|
||||
Full-domain redirects are allowed if they pass our CORS origin checks.
|
||||
|
||||
Otherwise use default (self.base_url if unspecified).
|
||||
"""
|
||||
if default is None:
|
||||
default = self.base_url
|
||||
# protect chrome users from mishandling unescaped backslashes.
|
||||
# \ is not valid in urls, but some browsers treat it as /
|
||||
# instead of %5C, causing `\\` to behave as `//`
|
||||
url = url.replace("\\", "%5C")
|
||||
parsed = urlparse(url)
|
||||
if parsed.netloc or not (parsed.path + '/').startswith(self.base_url):
|
||||
# require that next_url be absolute path within our path
|
||||
allow = False
|
||||
# OR pass our cross-origin check
|
||||
if parsed.netloc:
|
||||
# if full URL, run our cross-origin check:
|
||||
origin = '%s://%s' % (parsed.scheme, parsed.netloc)
|
||||
origin = origin.lower()
|
||||
if self.allow_origin:
|
||||
allow = self.allow_origin == origin
|
||||
elif self.allow_origin_pat:
|
||||
allow = bool(self.allow_origin_pat.match(origin))
|
||||
if not allow:
|
||||
# not allowed, use default
|
||||
self.log.warning("Not allowing login redirect to %r" % url)
|
||||
url = default
|
||||
self.redirect(url)
|
||||
|
||||
def get(self):
|
||||
if self.current_user:
|
||||
next_url = self.get_argument('next', default=self.base_url)
|
||||
self._redirect_safe(next_url)
|
||||
else:
|
||||
self._render()
|
||||
|
||||
@property
|
||||
def hashed_password(self):
|
||||
return self.password_from_settings(self.settings)
|
||||
|
||||
def passwd_check(self, a, b):
|
||||
return passwd_check(a, b)
|
||||
|
||||
def post(self):
|
||||
typed_password = self.get_argument('password', default=u'')
|
||||
new_password = self.get_argument('new_password', default=u'')
|
||||
|
||||
|
||||
|
||||
if self.get_login_available(self.settings):
|
||||
if self.passwd_check(self.hashed_password, typed_password) and not new_password:
|
||||
self.set_login_cookie(self, uuid.uuid4().hex)
|
||||
elif self.token and self.token == typed_password:
|
||||
self.set_login_cookie(self, uuid.uuid4().hex)
|
||||
if new_password and self.settings.get('allow_password_change'):
|
||||
config_dir = self.settings.get('config_dir')
|
||||
config_file = os.path.join(config_dir, 'jupyter_notebook_config.json')
|
||||
set_password(new_password, config_file=config_file)
|
||||
self.log.info("Wrote hashed password to %s" % config_file)
|
||||
else:
|
||||
self.set_status(401)
|
||||
self._render(message={'error': 'Invalid credentials'})
|
||||
return
|
||||
|
||||
|
||||
next_url = self.get_argument('next', default=self.base_url)
|
||||
self._redirect_safe(next_url)
|
||||
|
||||
@classmethod
|
||||
def set_login_cookie(cls, handler, user_id=None):
|
||||
"""Call this on handlers to set the login cookie for success"""
|
||||
cookie_options = handler.settings.get('cookie_options', {})
|
||||
cookie_options.setdefault('httponly', True)
|
||||
# tornado <4.2 has a bug that considers secure==True as soon as
|
||||
# 'secure' kwarg is passed to set_secure_cookie
|
||||
if handler.settings.get('secure_cookie', handler.request.protocol == 'https'):
|
||||
cookie_options.setdefault('secure', True)
|
||||
cookie_options.setdefault('path', handler.base_url)
|
||||
handler.set_secure_cookie(handler.cookie_name, user_id, **cookie_options)
|
||||
return user_id
|
||||
|
||||
auth_header_pat = re.compile('token\s+(.+)', re.IGNORECASE)
|
||||
|
||||
@classmethod
|
||||
def get_token(cls, handler):
|
||||
"""Get the user token from a request
|
||||
|
||||
Default:
|
||||
|
||||
- in URL parameters: ?token=<token>
|
||||
- in header: Authorization: token <token>
|
||||
"""
|
||||
|
||||
user_token = handler.get_argument('token', '')
|
||||
if not user_token:
|
||||
# get it from Authorization header
|
||||
m = cls.auth_header_pat.match(handler.request.headers.get('Authorization', ''))
|
||||
if m:
|
||||
user_token = m.group(1)
|
||||
return user_token
|
||||
|
||||
@classmethod
|
||||
def should_check_origin(cls, handler):
|
||||
"""Should the Handler check for CORS origin validation?
|
||||
|
||||
Origin check should be skipped for token-authenticated requests.
|
||||
|
||||
Returns:
|
||||
- True, if Handler must check for valid CORS origin.
|
||||
- False, if Handler should skip origin check since requests are token-authenticated.
|
||||
"""
|
||||
return not cls.is_token_authenticated(handler)
|
||||
|
||||
@classmethod
|
||||
def is_token_authenticated(cls, handler):
|
||||
"""Returns True if handler has been token authenticated. Otherwise, False.
|
||||
|
||||
Login with a token is used to signal certain things, such as:
|
||||
|
||||
- permit access to REST API
|
||||
- xsrf protection
|
||||
- skip origin-checks for scripts
|
||||
"""
|
||||
if getattr(handler, '_user_id', None) is None:
|
||||
# ensure get_user has been called, so we know if we're token-authenticated
|
||||
handler.get_current_user()
|
||||
return getattr(handler, '_token_authenticated', False)
|
||||
|
||||
@classmethod
|
||||
def get_user(cls, handler):
|
||||
"""Called by handlers.get_current_user for identifying the current user.
|
||||
|
||||
See tornado.web.RequestHandler.get_current_user for details.
|
||||
"""
|
||||
# Can't call this get_current_user because it will collide when
|
||||
# called on LoginHandler itself.
|
||||
if getattr(handler, '_user_id', None):
|
||||
return handler._user_id
|
||||
user_id = cls.get_user_token(handler)
|
||||
if user_id is None:
|
||||
get_secure_cookie_kwargs = handler.settings.get('get_secure_cookie_kwargs', {})
|
||||
user_id = handler.get_secure_cookie(handler.cookie_name, **get_secure_cookie_kwargs )
|
||||
else:
|
||||
cls.set_login_cookie(handler, user_id)
|
||||
# Record that the current request has been authenticated with a token.
|
||||
# Used in is_token_authenticated above.
|
||||
handler._token_authenticated = True
|
||||
if user_id is None:
|
||||
# If an invalid cookie was sent, clear it to prevent unnecessary
|
||||
# extra warnings. But don't do this on a request with *no* cookie,
|
||||
# because that can erroneously log you out (see gh-3365)
|
||||
if handler.get_cookie(handler.cookie_name) is not None:
|
||||
handler.log.warning("Clearing invalid/expired login cookie %s", handler.cookie_name)
|
||||
handler.clear_login_cookie()
|
||||
if not handler.login_available:
|
||||
# Completely insecure! No authentication at all.
|
||||
# No need to warn here, though; validate_security will have already done that.
|
||||
user_id = 'anonymous'
|
||||
|
||||
# cache value for future retrievals on the same request
|
||||
handler._user_id = user_id
|
||||
return user_id
|
||||
|
||||
@classmethod
|
||||
def get_user_token(cls, handler):
|
||||
"""Identify the user based on a token in the URL or Authorization header
|
||||
|
||||
Returns:
|
||||
- uuid if authenticated
|
||||
- None if not
|
||||
"""
|
||||
token = handler.token
|
||||
if not token:
|
||||
return
|
||||
# check login token from URL argument or Authorization header
|
||||
user_token = cls.get_token(handler)
|
||||
authenticated = False
|
||||
if user_token == token:
|
||||
# token-authenticated, set the login cookie
|
||||
handler.log.debug("Accepting token-authenticated connection from %s", handler.request.remote_ip)
|
||||
authenticated = True
|
||||
|
||||
if authenticated:
|
||||
return uuid.uuid4().hex
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
@classmethod
|
||||
def validate_security(cls, app, ssl_options=None):
|
||||
"""Check the notebook application's security.
|
||||
|
||||
Show messages, or abort if necessary, based on the security configuration.
|
||||
"""
|
||||
if not app.ip:
|
||||
warning = "WARNING: The notebook server is listening on all IP addresses"
|
||||
if ssl_options is None:
|
||||
app.log.warning(warning + " and not using encryption. This "
|
||||
"is not recommended.")
|
||||
if not app.password and not app.token:
|
||||
app.log.warning(warning + " and not using authentication. "
|
||||
"This is highly insecure and not recommended.")
|
||||
else:
|
||||
if not app.password and not app.token:
|
||||
app.log.warning(
|
||||
"All authentication is disabled."
|
||||
" Anyone who can connect to this server will be able to run code.")
|
||||
|
||||
@classmethod
|
||||
def password_from_settings(cls, settings):
|
||||
"""Return the hashed password from the tornado settings.
|
||||
|
||||
If there is no configured password, an empty string will be returned.
|
||||
"""
|
||||
return settings.get('password', u'')
|
||||
|
||||
@classmethod
|
||||
def get_login_available(cls, settings):
|
||||
"""Whether this LoginHandler is needed - and therefore whether the login page should be displayed."""
|
||||
return bool(cls.password_from_settings(settings) or settings.get('token'))
|
23
venv/Lib/site-packages/notebook/auth/logout.py
Normal file
23
venv/Lib/site-packages/notebook/auth/logout.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
"""Tornado handlers for logging out of the notebook.
|
||||
"""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from ..base.handlers import IPythonHandler
|
||||
|
||||
|
||||
class LogoutHandler(IPythonHandler):
|
||||
|
||||
def get(self):
|
||||
self.clear_login_cookie()
|
||||
if self.login_available:
|
||||
message = {'info': 'Successfully logged out.'}
|
||||
else:
|
||||
message = {'warning': 'Cannot log out. Notebook authentication '
|
||||
'is disabled.'}
|
||||
self.write(self.render_template('logout.html',
|
||||
message=message))
|
||||
|
||||
|
||||
default_handlers = [(r"/logout", LogoutHandler)]
|
172
venv/Lib/site-packages/notebook/auth/security.py
Normal file
172
venv/Lib/site-packages/notebook/auth/security.py
Normal file
|
@ -0,0 +1,172 @@
|
|||
"""
|
||||
Password generation for the Notebook.
|
||||
"""
|
||||
|
||||
from contextlib import contextmanager
|
||||
import getpass
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import traceback
|
||||
import warnings
|
||||
|
||||
from ipython_genutils.py3compat import cast_bytes, str_to_bytes, cast_unicode
|
||||
from traitlets.config import Config, ConfigFileNotFound, JSONFileConfigLoader
|
||||
from jupyter_core.paths import jupyter_config_dir
|
||||
|
||||
# Length of the salt in nr of hex chars, which implies salt_len * 4
|
||||
# bits of randomness.
|
||||
salt_len = 12
|
||||
|
||||
|
||||
def passwd(passphrase=None, algorithm='argon2'):
|
||||
"""Generate hashed password and salt for use in notebook configuration.
|
||||
|
||||
In the notebook configuration, set `c.NotebookApp.password` to
|
||||
the generated string.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
passphrase : str
|
||||
Password to hash. If unspecified, the user is asked to input
|
||||
and verify a password.
|
||||
algorithm : str
|
||||
Hashing algorithm to use (e.g, 'sha1' or any argument supported
|
||||
by :func:`hashlib.new`, or 'argon2').
|
||||
|
||||
Returns
|
||||
-------
|
||||
hashed_passphrase : str
|
||||
Hashed password, in the format 'hash_algorithm:salt:passphrase_hash'.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> passwd('mypassword')
|
||||
'sha1:7cf3:b7d6da294ea9592a9480c8f52e63cd42cfb9dd12'
|
||||
|
||||
"""
|
||||
if passphrase is None:
|
||||
for i in range(3):
|
||||
p0 = getpass.getpass('Enter password: ')
|
||||
p1 = getpass.getpass('Verify password: ')
|
||||
if p0 == p1:
|
||||
passphrase = p0
|
||||
break
|
||||
else:
|
||||
print('Passwords do not match.')
|
||||
else:
|
||||
raise ValueError('No matching passwords found. Giving up.')
|
||||
|
||||
if algorithm == 'argon2':
|
||||
from argon2 import PasswordHasher
|
||||
ph = PasswordHasher(
|
||||
memory_cost=10240,
|
||||
time_cost=10,
|
||||
parallelism=8,
|
||||
)
|
||||
h = ph.hash(passphrase)
|
||||
|
||||
return ':'.join((algorithm, cast_unicode(h, 'ascii')))
|
||||
else:
|
||||
h = hashlib.new(algorithm)
|
||||
salt = ('%0' + str(salt_len) + 'x') % random.getrandbits(4 * salt_len)
|
||||
h.update(cast_bytes(passphrase, 'utf-8') + str_to_bytes(salt, 'ascii'))
|
||||
|
||||
return ':'.join((algorithm, salt, h.hexdigest()))
|
||||
|
||||
|
||||
def passwd_check(hashed_passphrase, passphrase):
|
||||
"""Verify that a given passphrase matches its hashed version.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
hashed_passphrase : str
|
||||
Hashed password, in the format returned by `passwd`.
|
||||
passphrase : str
|
||||
Passphrase to validate.
|
||||
|
||||
Returns
|
||||
-------
|
||||
valid : bool
|
||||
True if the passphrase matches the hash.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from notebook.auth.security import passwd_check
|
||||
>>> passwd_check('argon2:...', 'mypassword')
|
||||
True
|
||||
|
||||
>>> passwd_check('argon2:...', 'otherpassword')
|
||||
False
|
||||
|
||||
>>> passwd_check('sha1:0e112c3ddfce:a68df677475c2b47b6e86d0467eec97ac5f4b85a',
|
||||
... 'mypassword')
|
||||
True
|
||||
"""
|
||||
if hashed_passphrase.startswith('argon2:'):
|
||||
import argon2
|
||||
import argon2.exceptions
|
||||
ph = argon2.PasswordHasher()
|
||||
try:
|
||||
return ph.verify(hashed_passphrase[7:], passphrase)
|
||||
except argon2.exceptions.VerificationError:
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
algorithm, salt, pw_digest = hashed_passphrase.split(':', 2)
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
try:
|
||||
h = hashlib.new(algorithm)
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
if len(pw_digest) == 0:
|
||||
return False
|
||||
|
||||
h.update(cast_bytes(passphrase, 'utf-8') + cast_bytes(salt, 'ascii'))
|
||||
|
||||
return h.hexdigest() == pw_digest
|
||||
|
||||
@contextmanager
|
||||
def persist_config(config_file=None, mode=0o600):
|
||||
"""Context manager that can be used to modify a config object
|
||||
|
||||
On exit of the context manager, the config will be written back to disk,
|
||||
by default with user-only (600) permissions.
|
||||
"""
|
||||
|
||||
if config_file is None:
|
||||
config_file = os.path.join(jupyter_config_dir(), 'jupyter_notebook_config.json')
|
||||
|
||||
os.makedirs(os.path.dirname(config_file), exist_ok=True)
|
||||
|
||||
loader = JSONFileConfigLoader(os.path.basename(config_file), os.path.dirname(config_file))
|
||||
try:
|
||||
config = loader.load_config()
|
||||
except ConfigFileNotFound:
|
||||
config = Config()
|
||||
|
||||
yield config
|
||||
|
||||
with io.open(config_file, 'w', encoding='utf8') as f:
|
||||
f.write(cast_unicode(json.dumps(config, indent=2)))
|
||||
|
||||
try:
|
||||
os.chmod(config_file, mode)
|
||||
except Exception as e:
|
||||
tb = traceback.format_exc()
|
||||
warnings.warn("Failed to set permissions on %s:\n%s" % (config_file, tb),
|
||||
RuntimeWarning)
|
||||
|
||||
|
||||
def set_password(password=None, config_file=None):
|
||||
"""Ask user for password, store it in notebook json configuration file"""
|
||||
|
||||
hashed_password = passwd(password)
|
||||
|
||||
with persist_config(config_file) as config:
|
||||
config.NotebookApp.password = hashed_password
|
0
venv/Lib/site-packages/notebook/auth/tests/__init__.py
Normal file
0
venv/Lib/site-packages/notebook/auth/tests/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
48
venv/Lib/site-packages/notebook/auth/tests/test_login.py
Normal file
48
venv/Lib/site-packages/notebook/auth/tests/test_login.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
"""Tests for login redirects"""
|
||||
|
||||
import requests
|
||||
from tornado.httputil import url_concat
|
||||
|
||||
from notebook.tests.launchnotebook import NotebookTestBase
|
||||
|
||||
|
||||
class LoginTest(NotebookTestBase):
|
||||
def login(self, next):
|
||||
first = requests.get(self.base_url() + "login")
|
||||
first.raise_for_status()
|
||||
resp = requests.post(
|
||||
url_concat(
|
||||
self.base_url() + "login",
|
||||
{'next': next},
|
||||
),
|
||||
allow_redirects=False,
|
||||
data={
|
||||
"password": self.token,
|
||||
"_xsrf": first.cookies.get("_xsrf", ""),
|
||||
},
|
||||
cookies=first.cookies,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.headers['Location']
|
||||
|
||||
def test_next_bad(self):
|
||||
for bad_next in (
|
||||
"//some-host",
|
||||
"//host" + self.url_prefix + "tree",
|
||||
"https://google.com",
|
||||
"/absolute/not/base_url",
|
||||
):
|
||||
url = self.login(next=bad_next)
|
||||
self.assertEqual(url, self.url_prefix)
|
||||
assert url
|
||||
|
||||
def test_next_ok(self):
|
||||
for next_path in (
|
||||
"tree/",
|
||||
"//" + self.url_prefix + "tree",
|
||||
"notebooks/notebook.ipynb",
|
||||
"tree//something",
|
||||
):
|
||||
expected = self.url_prefix + next_path
|
||||
actual = self.login(next=expected)
|
||||
self.assertEqual(actual, expected)
|
27
venv/Lib/site-packages/notebook/auth/tests/test_security.py
Normal file
27
venv/Lib/site-packages/notebook/auth/tests/test_security.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
# coding: utf-8
|
||||
from ..security import passwd, passwd_check
|
||||
import nose.tools as nt
|
||||
|
||||
def test_passwd_structure():
|
||||
p = passwd('passphrase')
|
||||
algorithm, hashed = p.split(':')
|
||||
nt.assert_equal(algorithm, 'argon2')
|
||||
nt.assert_true(hashed.startswith('$argon2id$'))
|
||||
|
||||
def test_roundtrip():
|
||||
p = passwd('passphrase')
|
||||
nt.assert_equal(passwd_check(p, 'passphrase'), True)
|
||||
|
||||
def test_bad():
|
||||
p = passwd('passphrase')
|
||||
nt.assert_equal(passwd_check(p, p), False)
|
||||
nt.assert_equal(passwd_check(p, 'a:b:c:d'), False)
|
||||
nt.assert_equal(passwd_check(p, 'a:b'), False)
|
||||
|
||||
def test_passwd_check_unicode():
|
||||
# GH issue #4524
|
||||
phash = u'sha1:23862bc21dd3:7a415a95ae4580582e314072143d9c382c491e4f'
|
||||
assert passwd_check(phash, u"łe¶ŧ←↓→")
|
||||
phash = (u'argon2:$argon2id$v=19$m=10240,t=10,p=8$'
|
||||
u'qjjDiZUofUVVnrVYxacnbA$l5pQq1bJ8zglGT2uXP6iOg')
|
||||
assert passwd_check(phash, u"łe¶ŧ←↓→")
|
Loading…
Add table
Add a link
Reference in a new issue