您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

270 行
9.9 KiB

  1. """passlib.handlers.misc - misc generic handlers
  2. """
  3. #=============================================================================
  4. # imports
  5. #=============================================================================
  6. # core
  7. import sys
  8. import logging; log = logging.getLogger(__name__)
  9. from warnings import warn
  10. # site
  11. # pkg
  12. from passlib.utils import to_native_str, str_consteq
  13. from passlib.utils.compat import unicode, u, unicode_or_bytes_types
  14. import passlib.utils.handlers as uh
  15. # local
  16. __all__ = [
  17. "unix_disabled",
  18. "unix_fallback",
  19. "plaintext",
  20. ]
  21. #=============================================================================
  22. # handler
  23. #=============================================================================
  24. class unix_fallback(uh.ifc.DisabledHash, uh.StaticHandler):
  25. """This class provides the fallback behavior for unix shadow files, and follows the :ref:`password-hash-api`.
  26. This class does not implement a hash, but instead provides fallback
  27. behavior as found in /etc/shadow on most unix variants.
  28. If used, should be the last scheme in the context.
  29. * this class will positively identify all hash strings.
  30. * for security, passwords will always hash to ``!``.
  31. * it rejects all passwords if the hash is NOT an empty string (``!`` or ``*`` are frequently used).
  32. * by default it rejects all passwords if the hash is an empty string,
  33. but if ``enable_wildcard=True`` is passed to verify(),
  34. all passwords will be allowed through if the hash is an empty string.
  35. .. deprecated:: 1.6
  36. This has been deprecated due to its "wildcard" feature,
  37. and will be removed in Passlib 1.8. Use :class:`unix_disabled` instead.
  38. """
  39. name = "unix_fallback"
  40. context_kwds = ("enable_wildcard",)
  41. @classmethod
  42. def identify(cls, hash):
  43. if isinstance(hash, unicode_or_bytes_types):
  44. return True
  45. else:
  46. raise uh.exc.ExpectedStringError(hash, "hash")
  47. def __init__(self, enable_wildcard=False, **kwds):
  48. warn("'unix_fallback' is deprecated, "
  49. "and will be removed in Passlib 1.8; "
  50. "please use 'unix_disabled' instead.",
  51. DeprecationWarning)
  52. super(unix_fallback, self).__init__(**kwds)
  53. self.enable_wildcard = enable_wildcard
  54. def _calc_checksum(self, secret):
  55. if self.checksum:
  56. # NOTE: hash will generally be "!", but we want to preserve
  57. # it in case it's something else, like "*".
  58. return self.checksum
  59. else:
  60. return u("!")
  61. @classmethod
  62. def verify(cls, secret, hash, enable_wildcard=False):
  63. uh.validate_secret(secret)
  64. if not isinstance(hash, unicode_or_bytes_types):
  65. raise uh.exc.ExpectedStringError(hash, "hash")
  66. elif hash:
  67. return False
  68. else:
  69. return enable_wildcard
  70. _MARKER_CHARS = u("*!")
  71. _MARKER_BYTES = b"*!"
  72. class unix_disabled(uh.ifc.DisabledHash, uh.MinimalHandler):
  73. """This class provides disabled password behavior for unix shadow files,
  74. and follows the :ref:`password-hash-api`.
  75. This class does not implement a hash, but instead matches the "disabled account"
  76. strings found in ``/etc/shadow`` on most Unix variants. "encrypting" a password
  77. will simply return the disabled account marker. It will reject all passwords,
  78. no matter the hash string. The :meth:`~passlib.ifc.PasswordHash.hash`
  79. method supports one optional keyword:
  80. :type marker: str
  81. :param marker:
  82. Optional marker string which overrides the platform default
  83. used to indicate a disabled account.
  84. If not specified, this will default to ``"*"`` on BSD systems,
  85. and use the Linux default ``"!"`` for all other platforms.
  86. (:attr:`!unix_disabled.default_marker` will contain the default value)
  87. .. versionadded:: 1.6
  88. This class was added as a replacement for the now-deprecated
  89. :class:`unix_fallback` class, which had some undesirable features.
  90. """
  91. name = "unix_disabled"
  92. setting_kwds = ("marker",)
  93. context_kwds = ()
  94. _disable_prefixes = tuple(str(_MARKER_CHARS))
  95. # TODO: rename attr to 'marker'...
  96. if 'bsd' in sys.platform: # pragma: no cover -- runtime detection
  97. default_marker = u("*")
  98. else:
  99. # use the linux default for other systems
  100. # (glibc also supports adding old hash after the marker
  101. # so it can be restored later).
  102. default_marker = u("!")
  103. @classmethod
  104. def using(cls, marker=None, **kwds):
  105. subcls = super(unix_disabled, cls).using(**kwds)
  106. if marker is not None:
  107. if not cls.identify(marker):
  108. raise ValueError("invalid marker: %r" % marker)
  109. subcls.default_marker = marker
  110. return subcls
  111. @classmethod
  112. def identify(cls, hash):
  113. # NOTE: technically, anything in the /etc/shadow password field
  114. # which isn't valid crypt() output counts as "disabled".
  115. # but that's rather ambiguous, and it's hard to predict what
  116. # valid output is for unknown crypt() implementations.
  117. # so to be on the safe side, we only match things *known*
  118. # to be disabled field indicators, and will add others
  119. # as they are found. things beginning w/ "$" should *never* match.
  120. #
  121. # things currently matched:
  122. # * linux uses "!"
  123. # * bsd uses "*"
  124. # * linux may use "!" + hash to disable but preserve original hash
  125. # * linux counts empty string as "any password";
  126. # this code recognizes it, but treats it the same as "!"
  127. if isinstance(hash, unicode):
  128. start = _MARKER_CHARS
  129. elif isinstance(hash, bytes):
  130. start = _MARKER_BYTES
  131. else:
  132. raise uh.exc.ExpectedStringError(hash, "hash")
  133. return not hash or hash[0] in start
  134. @classmethod
  135. def verify(cls, secret, hash):
  136. uh.validate_secret(secret)
  137. if not cls.identify(hash): # handles typecheck
  138. raise uh.exc.InvalidHashError(cls)
  139. return False
  140. @classmethod
  141. def hash(cls, secret, **kwds):
  142. if kwds:
  143. uh.warn_hash_settings_deprecation(cls, kwds)
  144. return cls.using(**kwds).hash(secret)
  145. uh.validate_secret(secret)
  146. marker = cls.default_marker
  147. assert marker and cls.identify(marker)
  148. return to_native_str(marker, param="marker")
  149. @uh.deprecated_method(deprecated="1.7", removed="2.0")
  150. @classmethod
  151. def genhash(cls, secret, config, marker=None):
  152. if not cls.identify(config):
  153. raise uh.exc.InvalidHashError(cls)
  154. elif config:
  155. # preserve the existing str,since it might contain a disabled password hash ("!" + hash)
  156. uh.validate_secret(secret)
  157. return to_native_str(config, param="config")
  158. else:
  159. if marker is not None:
  160. cls = cls.using(marker=marker)
  161. return cls.hash(secret)
  162. @classmethod
  163. def disable(cls, hash=None):
  164. out = cls.hash("")
  165. if hash is not None:
  166. hash = to_native_str(hash, param="hash")
  167. if cls.identify(hash):
  168. # extract original hash, so that we normalize marker
  169. hash = cls.enable(hash)
  170. if hash:
  171. out += hash
  172. return out
  173. @classmethod
  174. def enable(cls, hash):
  175. hash = to_native_str(hash, param="hash")
  176. for prefix in cls._disable_prefixes:
  177. if hash.startswith(prefix):
  178. orig = hash[len(prefix):]
  179. if orig:
  180. return orig
  181. else:
  182. raise ValueError("cannot restore original hash")
  183. raise uh.exc.InvalidHashError(cls)
  184. class plaintext(uh.MinimalHandler):
  185. """This class stores passwords in plaintext, and follows the :ref:`password-hash-api`.
  186. The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
  187. following additional contextual keyword:
  188. :type encoding: str
  189. :param encoding:
  190. This controls the character encoding to use (defaults to ``utf-8``).
  191. This encoding will be used to encode :class:`!unicode` passwords
  192. under Python 2, and decode :class:`!bytes` hashes under Python 3.
  193. .. versionchanged:: 1.6
  194. The ``encoding`` keyword was added.
  195. """
  196. # NOTE: this is subclassed by ldap_plaintext
  197. name = "plaintext"
  198. setting_kwds = ()
  199. context_kwds = ("encoding",)
  200. default_encoding = "utf-8"
  201. @classmethod
  202. def identify(cls, hash):
  203. if isinstance(hash, unicode_or_bytes_types):
  204. return True
  205. else:
  206. raise uh.exc.ExpectedStringError(hash, "hash")
  207. @classmethod
  208. def hash(cls, secret, encoding=None):
  209. uh.validate_secret(secret)
  210. if not encoding:
  211. encoding = cls.default_encoding
  212. return to_native_str(secret, encoding, "secret")
  213. @classmethod
  214. def verify(cls, secret, hash, encoding=None):
  215. if not encoding:
  216. encoding = cls.default_encoding
  217. hash = to_native_str(hash, encoding, "hash")
  218. if not cls.identify(hash):
  219. raise uh.exc.InvalidHashError(cls)
  220. return str_consteq(cls.hash(secret, encoding), hash)
  221. @uh.deprecated_method(deprecated="1.7", removed="2.0")
  222. @classmethod
  223. def genconfig(cls):
  224. return cls.hash("")
  225. @uh.deprecated_method(deprecated="1.7", removed="2.0")
  226. @classmethod
  227. def genhash(cls, secret, config, encoding=None):
  228. # NOTE: 'config' is ignored, as this hash has no salting / etc
  229. if not cls.identify(config):
  230. raise uh.exc.InvalidHashError(cls)
  231. return cls.hash(secret, encoding=encoding)
  232. #=============================================================================
  233. # eof
  234. #=============================================================================