security.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. """
  2. Password generation for the Notebook.
  3. """
  4. from contextlib import contextmanager
  5. import getpass
  6. import hashlib
  7. import io
  8. import json
  9. import os
  10. import random
  11. import traceback
  12. import warnings
  13. from ipython_genutils.py3compat import cast_bytes, str_to_bytes, cast_unicode
  14. from traitlets.config import Config, ConfigFileNotFound, JSONFileConfigLoader
  15. from jupyter_core.paths import jupyter_config_dir
  16. # Length of the salt in nr of hex chars, which implies salt_len * 4
  17. # bits of randomness.
  18. salt_len = 12
  19. def passwd(passphrase=None, algorithm='sha1'):
  20. """Generate hashed password and salt for use in notebook configuration.
  21. In the notebook configuration, set `c.NotebookApp.password` to
  22. the generated string.
  23. Parameters
  24. ----------
  25. passphrase : str
  26. Password to hash. If unspecified, the user is asked to input
  27. and verify a password.
  28. algorithm : str
  29. Hashing algorithm to use (e.g, 'sha1' or any argument supported
  30. by :func:`hashlib.new`).
  31. Returns
  32. -------
  33. hashed_passphrase : str
  34. Hashed password, in the format 'hash_algorithm:salt:passphrase_hash'.
  35. Examples
  36. --------
  37. >>> passwd('mypassword')
  38. 'sha1:7cf3:b7d6da294ea9592a9480c8f52e63cd42cfb9dd12'
  39. """
  40. if passphrase is None:
  41. for i in range(3):
  42. p0 = getpass.getpass('Enter password: ')
  43. p1 = getpass.getpass('Verify password: ')
  44. if p0 == p1:
  45. passphrase = p0
  46. break
  47. else:
  48. print('Passwords do not match.')
  49. else:
  50. raise ValueError('No matching passwords found. Giving up.')
  51. h = hashlib.new(algorithm)
  52. salt = ('%0' + str(salt_len) + 'x') % random.getrandbits(4 * salt_len)
  53. h.update(cast_bytes(passphrase, 'utf-8') + str_to_bytes(salt, 'ascii'))
  54. return ':'.join((algorithm, salt, h.hexdigest()))
  55. def passwd_check(hashed_passphrase, passphrase):
  56. """Verify that a given passphrase matches its hashed version.
  57. Parameters
  58. ----------
  59. hashed_passphrase : str
  60. Hashed password, in the format returned by `passwd`.
  61. passphrase : str
  62. Passphrase to validate.
  63. Returns
  64. -------
  65. valid : bool
  66. True if the passphrase matches the hash.
  67. Examples
  68. --------
  69. >>> from notebook.auth.security import passwd_check
  70. >>> passwd_check('sha1:0e112c3ddfce:a68df677475c2b47b6e86d0467eec97ac5f4b85a',
  71. ... 'mypassword')
  72. True
  73. >>> passwd_check('sha1:0e112c3ddfce:a68df677475c2b47b6e86d0467eec97ac5f4b85a',
  74. ... 'anotherpassword')
  75. False
  76. """
  77. try:
  78. algorithm, salt, pw_digest = hashed_passphrase.split(':', 2)
  79. except (ValueError, TypeError):
  80. return False
  81. try:
  82. h = hashlib.new(algorithm)
  83. except ValueError:
  84. return False
  85. if len(pw_digest) == 0:
  86. return False
  87. h.update(cast_bytes(passphrase, 'utf-8') + cast_bytes(salt, 'ascii'))
  88. return h.hexdigest() == pw_digest
  89. @contextmanager
  90. def persist_config(config_file=None, mode=0o600):
  91. """Context manager that can be used to modify a config object
  92. On exit of the context manager, the config will be written back to disk,
  93. by default with user-only (600) permissions.
  94. """
  95. if config_file is None:
  96. config_file = os.path.join(jupyter_config_dir(), 'jupyter_notebook_config.json')
  97. loader = JSONFileConfigLoader(os.path.basename(config_file), os.path.dirname(config_file))
  98. try:
  99. config = loader.load_config()
  100. except ConfigFileNotFound:
  101. config = Config()
  102. yield config
  103. with io.open(config_file, 'w', encoding='utf8') as f:
  104. f.write(cast_unicode(json.dumps(config, indent=2)))
  105. try:
  106. os.chmod(config_file, mode)
  107. except Exception as e:
  108. tb = traceback.format_exc()
  109. warnings.warn("Failed to set permissions on %s:\n%s" % (config_file, tb),
  110. RuntimeWarning)
  111. def set_password(password=None, config_file=None):
  112. """Ask user for password, store it in notebook json configuration file"""
  113. hashed_password = passwd(password)
  114. with persist_config(config_file) as config:
  115. config.NotebookApp.password = hashed_password