def test_bad_algorithm(self): msg = ( "Unknown password hashing algorithm '%s'. Did you specify it in " "the PASSWORD_HASHERS setting?" ) with self.assertRaisesMessage(ValueError, msg % 'lolcat'): make_password('lètmein', hasher='lolcat') with self.assertRaisesMessage(ValueError, msg % 'lolcat'): identify_hasher('lolcat$salt$hash')
def test_unusable(self): encoded = make_password(None) self.assertEqual(len(encoded), len(UNUSABLE_PASSWORD_PREFIX) + UNUSABLE_PASSWORD_SUFFIX_LENGTH) self.assertFalse(is_password_usable(encoded)) self.assertFalse(check_password(None, encoded)) self.assertFalse(check_password(encoded, encoded)) self.assertFalse(check_password(UNUSABLE_PASSWORD_PREFIX, encoded)) self.assertFalse(check_password('', encoded)) self.assertFalse(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) with self.assertRaises(ValueError): identify_hasher(encoded) # Assert that the unusable passwords actually contain a random part. # This might fail one day due to a hash collision. self.assertNotEqual(encoded, make_password(None), "Random password collision?")
def test_crypt(self): encoded = make_password("lètmei", "ab", "crypt") self.assertEqual(encoded, "crypt$$ab1Hv2Lg7ltQo") self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password("lètmei", encoded)) self.assertFalse(check_password("lètmeiz", encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "crypt")
def test_md5(self): encoded = make_password("lètmein", "seasalt", "md5") self.assertEqual(encoded, "md5$seasalt$3f86d0d3d465b7b458c231bf3555c0e3") self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password("lètmein", encoded)) self.assertFalse(check_password("lètmeinz", encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "md5")
def test_sha1(self): encoded = make_password("lètmein", "seasalt", "sha1") self.assertEqual(encoded, "sha1$seasalt$cff36ea83f5706ce9aa7454e63e431fc726b2dc8") self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password("lètmein", encoded)) self.assertFalse(check_password("lètmeinz", encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "sha1")
def test_pkbdf2(self): encoded = make_password("lètmein", "seasalt", "pbkdf2_sha256") self.assertEqual(encoded, "pbkdf2_sha256$10000$seasalt$CWWFdHOWwPnki7HvkcqN9iA2T3KLW1cf2uZ5kvArtVY=") self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password("lètmein", encoded)) self.assertFalse(check_password("lètmeinz", encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "pbkdf2_sha256")
def test_bcrypt(self): encoded = make_password("lètmein", hasher="bcrypt") self.assertTrue(is_password_usable(encoded)) self.assertTrue(encoded.startswith("bcrypt$")) self.assertTrue(check_password("lètmein", encoded)) self.assertFalse(check_password("lètmeinz", encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "bcrypt")
def test_bcrypt_sha256(self): encoded = make_password('lètmein', hasher='bcrypt_sha256') self.assertTrue(is_password_usable(encoded)) self.assertTrue(encoded.startswith('bcrypt_sha256$')) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "bcrypt_sha256") # Verify that password truncation no longer works password = ('VSK0UYV6FFQVZ0KG88DYN9WADAADZO1CTSIVDJUNZSUML6IBX7LN7ZS3R5' 'JGB3RGZ7VI7G7DJQ9NI8BQFSRPTG6UWTTVESA5ZPUN') encoded = make_password(password, hasher='bcrypt_sha256') self.assertTrue(check_password(password, encoded)) self.assertFalse(check_password(password[:72], encoded)) # Blank passwords blank_encoded = make_password('', hasher='bcrypt_sha256') self.assertTrue(blank_encoded.startswith('bcrypt_sha256$')) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded)) # Long password self.assertRaises( ValueError, make_password, b"1" * (MAXIMUM_PASSWORD_LENGTH + 1), hasher="bcrypt_sha256", )
def test_unsalted_md5(self): encoded = make_password('lètmein', '', 'unsalted_md5') self.assertEqual(encoded, '88a434c88cca4e900f7874cd98123f43') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_md5") # Alternate unsalted syntax alt_encoded = "md5$$%s" % encoded self.assertTrue(is_password_usable(alt_encoded)) self.assertTrue(check_password('lètmein', alt_encoded)) self.assertFalse(check_password('lètmeinz', alt_encoded)) # Blank passwords blank_encoded = make_password('', '', 'unsalted_md5') self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded)) # Long password self.assertRaises( ValueError, make_password, b"1" * (MAXIMUM_PASSWORD_LENGTH + 1), "", "unsalted_md5", )
def test_bcrypt(self): encoded = make_password('lètmein', hasher='bcrypt') self.assertTrue(is_password_usable(encoded)) self.assertTrue(encoded.startswith('bcrypt$')) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "bcrypt")
def test_crypt(self): encoded = make_password('lètmei', 'ab', 'crypt') self.assertEqual(encoded, 'crypt$$ab1Hv2Lg7ltQo') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmei', encoded)) self.assertFalse(check_password('lètmeiz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "crypt")
def test_crypt(self): encoded = make_password('letmein', 'ab', 'crypt') self.assertEqual(encoded, 'crypt$$abN/qM.L/H8EQ') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password(u'letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "crypt")
def test_unsalted_md5(self): encoded = make_password('letmein', 'seasalt', 'unsalted_md5') self.assertEqual(encoded, '0d107d09f5bbe40cade3de5c71e9e9b7') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password(u'letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_md5")
def clean_password(self, ix=''): if ix != '': ix = '_%i' % ix password = self.cleaned_data.get('password%s' % ix) if password: # lazy loading because of passlib from django.contrib.auth.hashers import identify_hasher self.password_provided = True try: identify_hasher(password) except ValueError: raise forms.ValidationError( self.error_messages['bad_hash'], code='bad_hash', ) return password
def test_unsalted_md5(self): encoded = make_password('lètmein', 'seasalt', 'unsalted_md5') self.assertEqual(encoded, '88a434c88cca4e900f7874cd98123f43') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_md5")
def render(self, name, value, attrs=None, renderer=None): final_attrs = flatatt(self.build_attrs(attrs)) if not value or not is_password_usable(value): summary = ugettext("No password set.") else: try: identify_hasher(value) except ValueError: summary = ugettext("Invalid password format or unknown" " hashing algorithm.") else: summary = ugettext('*************') return format_html('<div{attrs}><strong>{summary}</strong></div>', attrs=final_attrs, summary=summary)
def test_sha1(self): encoded = make_password('letmein', 'seasalt', 'sha1') self.assertEqual(encoded, 'sha1$seasalt$fec3530984afba6bade3347b7140d1a7da7da8c7') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password(u'letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "sha1")
def test_md5(self): encoded = make_password('letmein', 'seasalt', 'md5') self.assertEqual(encoded, 'md5$seasalt$f5531bef9f3687d0ccf0f617f0e25573') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password(u'letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "md5")
def render(self, name, value, attrs): from django.forms.util import flatatt final_attrs = flatatt(self.build_attrs(attrs)) if not value or value == UNUSABLE_PASSWORD: summary = ugettext("No password set.") else: try: identify_hasher(value) except ValueError: summary = ugettext("Invalid password format or unknown" " hashing algorithm.") else: summary = ugettext('*************') return format_html('<div{attrs}><strong>{summary}</strong></div>', attrs=final_attrs, summary=summary)
def test_pkbdf2(self): encoded = make_password('letmein', 'seasalt', 'pbkdf2_sha256') self.assertEqual(encoded, 'pbkdf2_sha256$10000$seasalt$FQCNpiZpTb0zub+HBsH6TOwyRxJ19FwvjbweatNmK/Y=') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password(u'letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "pbkdf2_sha256")
def test_unsalted_sha1(self): encoded = make_password('lètmein', '', 'unsalted_sha1') self.assertEqual(encoded, 'sha1$$6d138ca3ae545631b3abd71a4f076ce759c5700b') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_sha1") # Raw SHA1 isn't acceptable alt_encoded = encoded[6:] self.assertFalse(check_password('lètmein', alt_encoded))
def render(self, name, value, attrs): try: from django.forms.utils import flatatt except ImportError: from django.forms.util import flatatt # Django < 1.7 final_attrs = flatatt(self.build_attrs(attrs)) if not value or not is_password_usable(value): summary = _("No password set.") else: try: identify_hasher(value) except ValueError: summary = _("Invalid password format or unknown" " hashing algorithm.") else: summary = _('*************') return format_html('<div{attrs}><strong>{summary}</strong></div>', attrs=final_attrs, summary=summary)
def test_unsalted_md5(self): encoded = make_password('lètmein', '', 'unsalted_md5') self.assertEqual(encoded, '88a434c88cca4e900f7874cd98123f43') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_md5") # Alternate unsalted syntax alt_encoded = "md5$$%s" % encoded self.assertTrue(is_password_usable(alt_encoded)) self.assertTrue(check_password('lètmein', alt_encoded)) self.assertFalse(check_password('lètmeinz', alt_encoded))
def test_unsalted_sha1(self): encoded = make_password('lètmein', 'seasalt', 'unsalted_sha1') self.assertEqual(encoded, '38474bd98757137304be00938b15cef5b8ad9c98') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password(u'lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_sha1") # Alternate unsalted syntax alt_encoded = "sha1$$%s" % encoded self.assertTrue(is_password_usable(alt_encoded)) self.assertTrue(check_password(u'lètmein', alt_encoded)) self.assertFalse(check_password('lètmeinz', alt_encoded))
def render(self, name, value, attrs=None): """ Redefine the render to have an error message if there is any error else an empty string (''). """ encoded = value final_attrs = self.build_attrs(attrs) if not encoded or encoded.startswith(UNUSABLE_PASSWORD_PREFIX): summary = mark_safe("<strong>%s</strong>" % _( 'oscm_admin_noPasswordSet')) else: try: identify_hasher(encoded) except ValueError: summary = mark_safe( "<strong>%s</strong>" % _( 'oscm_admin_invalidPasswordFormatOrUnknownHashAlgo')) else: # Return empty string summary = '' return format_html("<div{0}>{1}</div>", flatatt(final_attrs), summary)
def test_pbkdf2(self): encoded = make_password('lètmein', 'seasalt', 'pbkdf2_sha256') self.assertEqual(encoded, 'pbkdf2_sha256$100000$seasalt$BNZ6eyaNc8qFTJPjrAq99hSYb73EgAdytAtdBg2Sdcc=') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "pbkdf2_sha256") # Blank passwords blank_encoded = make_password('', 'seasalt', 'pbkdf2_sha256') self.assertTrue(blank_encoded.startswith('pbkdf2_sha256$')) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded))
def test_pkbdf2(self): encoded = make_password("lètmein", "seasalt", "pbkdf2_sha256") self.assertEqual(encoded, "pbkdf2_sha256$20000$seasalt$oBSd886ysm3AqYun62DOdin8YcfbU1z9cksZSuLP9r0=") self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password("lètmein", encoded)) self.assertFalse(check_password("lètmeinz", encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "pbkdf2_sha256") # Blank passwords blank_encoded = make_password("", "seasalt", "pbkdf2_sha256") self.assertTrue(blank_encoded.startswith("pbkdf2_sha256$")) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password("", blank_encoded)) self.assertFalse(check_password(" ", blank_encoded))
def test_pbkdf2(self): encoded = make_password('lètmein', 'seasalt', 'pbkdf2_sha256') self.assertEqual(encoded, 'pbkdf2_sha256$36000$seasalt$mEUPPFJkT/xtwDU8rB7Q+puHRZnR07WRjerTkt/3HI0=') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "pbkdf2_sha256") # Blank passwords blank_encoded = make_password('', 'seasalt', 'pbkdf2_sha256') self.assertTrue(blank_encoded.startswith('pbkdf2_sha256$')) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded))
def test_md5(self): encoded = make_password('lètmein', 'seasalt', 'md5') self.assertEqual(encoded, 'md5$seasalt$3f86d0d3d465b7b458c231bf3555c0e3') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "md5") # Blank passwords blank_encoded = make_password('', 'seasalt', 'md5') self.assertTrue(blank_encoded.startswith('md5$')) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded))
def test_sha1(self): encoded = make_password('lètmein', 'seasalt', 'sha1') self.assertEqual(encoded, 'sha1$seasalt$cff36ea83f5706ce9aa7454e63e431fc726b2dc8') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "sha1") # Blank passwords blank_encoded = make_password('', 'seasalt', 'sha1') self.assertTrue(blank_encoded.startswith('sha1$')) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded))
def test_unsalted_sha1(self): encoded = make_password('lètmein', '', 'unsalted_sha1') self.assertEqual(encoded, 'sha1$$6d138ca3ae545631b3abd71a4f076ce759c5700b') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_sha1") # Raw SHA1 isn't acceptable alt_encoded = encoded[6:] self.assertFalse(check_password('lètmein', alt_encoded)) # Blank passwords blank_encoded = make_password('', '', 'unsalted_sha1') self.assertTrue(blank_encoded.startswith('sha1$')) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded))
def test_sha512(self): encoded = make_password('lètmein', 'seasalt', 'sha512') assert encoded == ( 'sha512$seasalt$16bf4502ffdfce9551b90319d06674e6faa3e174144123d' '392d94470ebf0aa77096b871f9e84f60ed2bac2f10f755368b068e52547e04' '35fef8b4f6ca237d7d8') assert is_password_usable(encoded) assert check_password('lètmein', encoded) assert not check_password('lètmeinz', encoded) assert identify_hasher(encoded).algorithm == "sha512" # Blank passwords blank_encoded = make_password('', 'seasalt', 'sha512') assert blank_encoded.startswith('sha512$') assert is_password_usable(blank_encoded) assert check_password('', blank_encoded) assert not check_password(' ', blank_encoded)
def test_scrypt(self): encoded = make_password("lètmein", "seasalt", "scrypt") self.assertEqual( encoded, "scrypt$16384$seasalt$8$1$Qj3+9PPyRjSJIebHnG81TMjsqtaIGxNQG/aEB/NY" "afTJ7tibgfYz71m0ldQESkXFRkdVCBhhY8mx7rQwite/Pw==", ) self.assertIs(is_password_usable(encoded), True) self.assertIs(check_password("lètmein", encoded), True) self.assertIs(check_password("lètmeinz", encoded), False) self.assertEqual(identify_hasher(encoded).algorithm, "scrypt") # Blank passwords. blank_encoded = make_password("", "seasalt", "scrypt") self.assertIs(blank_encoded.startswith("scrypt$"), True) self.assertIs(is_password_usable(blank_encoded), True) self.assertIs(check_password("", blank_encoded), True) self.assertIs(check_password(" ", blank_encoded), False)
def test_unsalted_md5(self): encoded = make_password('lètmein', '', 'unsalted_md5') self.assertEqual(encoded, '88a434c88cca4e900f7874cd98123f43') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_md5") # Alternate unsalted syntax alt_encoded = "md5$$%s" % encoded self.assertTrue(is_password_usable(alt_encoded)) self.assertTrue(check_password('lètmein', alt_encoded)) self.assertFalse(check_password('lètmeinz', alt_encoded)) # Blank passwords blank_encoded = make_password('', '', 'unsalted_md5') self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded))
def test_unsalted_sha1(self): encoded = make_password('lètmein', '', 'unsalted_sha1') self.assertEqual(encoded, 'sha1$$6d138ca3ae545631b3abd71a4f076ce759c5700b') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_sha1") # Raw SHA1 isn't acceptable alt_encoded = encoded[6:] self.assertFalse(check_password('lètmein', alt_encoded)) # Long password self.assertRaises( ValueError, make_password, b"1" * (MAXIMUM_PASSWORD_LENGTH + 1), "", "unslated_sha1", )
def render(self, name, value, attrs): encoded = value if not is_password_usable(encoded): return "None" final_attrs = self.build_attrs(attrs) try: hasher = identify_hasher(encoded) except ValueError: summary = "<strong>Invalid password format or unknown hashing algorithm.</strong>" else: summary = "" for key, value in hasher.safe_summary(encoded).iteritems(): summary += "<strong>%(key)s</strong>: %(value)s " % {"key": ugettext(key), "value": value} return mark_safe("<div%(attrs)s>%(summary)s</div>" % {"attrs": flatatt(final_attrs), "summary": summary})
def password_check(value, user): """ Check that the password passes the following validation: Password is long enough (based on configuration setting) Password contains at least one digit Password contains at least one letter Password contains at least one uppercase character Password contains at least one special character Password has not been reused in a configurable number of password cycles """ if len(value) < settings.AUTH_PASSWORD_LENGTH: raise serializers.ValidationError('Password is too short') # check for digit if not any(char.isdigit() for char in value): raise serializers.ValidationError( 'Password must contain at least 1 digit.') # check for letter if not any(char.isalpha() for char in value): raise serializers.ValidationError( 'Password must contain at least 1 letter.') # check for upper case letter if not any(char.isupper() for char in value): raise serializers.ValidationError( 'Password must contain at least 1 uppercase letter.') # check for symbol if not any(not char.isalnum() for char in value): raise serializers.ValidationError( 'Password must contain at least 1 special character.') # new users won't have a password history at this point if user is not None: prior_passwords = PasswordHistory.objects.filter( user_id=user.id).order_by( 'created_at').reverse()[:settings.AUTH_PASSWORD_HISTORY_COUNT] for p in prior_passwords: hasher = identify_hasher(p.password) if hasher.verify(value, p.password): raise serializers.ValidationError( 'Password can not have been one of the last %d passwords' % settings.AUTH_PASSWORD_HISTORY_COUNT) return value
def test_pbkdf2(self): encoded = make_password('lètmein', 'seasalt', 'pbkdf2_sha256') self.assertEqual(encoded, 'pbkdf2_sha256$320000$seasalt$Toj2II2rBvFiGQcPmUml1Nlni2UtvyRWwz/jz4q6q/4=') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "pbkdf2_sha256") # Blank passwords blank_encoded = make_password('', 'seasalt', 'pbkdf2_sha256') self.assertTrue(blank_encoded.startswith('pbkdf2_sha256$')) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded)) # Salt entropy check. hasher = get_hasher('pbkdf2_sha256') encoded_weak_salt = make_password('lètmein', 'iodizedsalt', 'pbkdf2_sha256') encoded_strong_salt = make_password('lètmein', hasher.salt(), 'pbkdf2_sha256') self.assertIs(hasher.must_update(encoded_weak_salt), True) self.assertIs(hasher.must_update(encoded_strong_salt), False)
def test_sha1(self): encoded = make_password('lètmein', 'seasalt', 'sha1') self.assertEqual(encoded, 'sha1$seasalt$cff36ea83f5706ce9aa7454e63e431fc726b2dc8') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "sha1") # Blank passwords blank_encoded = make_password('', 'seasalt', 'sha1') self.assertTrue(blank_encoded.startswith('sha1$')) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded)) # Salt entropy check. hasher = get_hasher('sha1') encoded_weak_salt = make_password('lètmein', 'iodizedsalt', 'sha1') encoded_strong_salt = make_password('lètmein', hasher.salt(), 'sha1') self.assertIs(hasher.must_update(encoded_weak_salt), True) self.assertIs(hasher.must_update(encoded_strong_salt), False)
def test_md5(self): encoded = make_password("lètmein", "seasalt", "md5") self.assertEqual(encoded, "md5$seasalt$3f86d0d3d465b7b458c231bf3555c0e3") self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password("lètmein", encoded)) self.assertFalse(check_password("lètmeinz", encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "md5") # Blank passwords blank_encoded = make_password("", "seasalt", "md5") self.assertTrue(blank_encoded.startswith("md5$")) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password("", blank_encoded)) self.assertFalse(check_password(" ", blank_encoded)) # Salt entropy check. hasher = get_hasher("md5") encoded_weak_salt = make_password("lètmein", "iodizedsalt", "md5") encoded_strong_salt = make_password("lètmein", hasher.salt(), "md5") self.assertIs(hasher.must_update(encoded_weak_salt), True) self.assertIs(hasher.must_update(encoded_strong_salt), False)
def import_hash(algorithm, hash): """Import a hash as given by the RestAuth import data format. Raises ValueError if the hasher cannot be identified. """ if algorithm == 'django': return hash try: import hashers_passlib hasher = getattr(hashers_passlib, str(algorithm))() except AttributeError: try: from hashers_passlib import converters hasher = getattr(hashers_passlib, str(converters))() except AttributeError: hasher = identify_hasher(algorithm) # try to get from Django return hasher.from_orig(hash)
def test_argon2(self): encoded = make_password('lètmein', hasher='argon2') self.assertTrue(is_password_usable(encoded)) self.assertTrue(encoded.startswith('argon2$')) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, 'argon2') # Blank passwords blank_encoded = make_password('', hasher='argon2') self.assertTrue(blank_encoded.startswith('argon2$')) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded)) # Old hashes without version attribute encoded = ( 'argon2$argon2i$m=8,t=1,p=1$c29tZXNhbHQ$gwQOXSNhxiOxPOA0+PY10P9QFO' '4NAYysnqRt1GSQLE55m+2GYDt9FEjPMHhP2Cuf0nOEXXMocVrsJAtNSsKyfg') self.assertTrue(check_password('secret', encoded)) self.assertFalse(check_password('wrong', encoded))
def render(self, name, value, attrs): encoded = value final_attrs = self.build_attrs(attrs) if not encoded or encoded.startswith(UNUSABLE_PASSWORD_PREFIX): summary = mark_safe("<strong>%s</strong>" % ugettext("No password set.")) else: try: hasher = identify_hasher(encoded) except ValueError: summary = mark_safe("<strong>%s</strong>" % ugettext( "Invalid password format or unknown hashing algorithm.")) else: summary = format_html_join( '', "<strong>{0}</strong>: {1} ", ((ugettext(key), value) for key, value in hasher.safe_summary(encoded).items())) return format_html("<div{0}>{1}</div>", flatatt(final_attrs), summary)
def test_bcrypt_sha256(self): encoded = make_password('lètmein', hasher='bcrypt_sha256') self.assertTrue(is_password_usable(encoded)) self.assertTrue(encoded.startswith('bcrypt_sha256$')) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "bcrypt_sha256") # Verify that password truncation no longer works password = ('VSK0UYV6FFQVZ0KG88DYN9WADAADZO1CTSIVDJUNZSUML6IBX7LN7ZS3R5' 'JGB3RGZ7VI7G7DJQ9NI8BQFSRPTG6UWTTVESA5ZPUN') encoded = make_password(password, hasher='bcrypt_sha256') self.assertTrue(check_password(password, encoded)) self.assertFalse(check_password(password[:72], encoded)) # Blank passwords blank_encoded = make_password('', hasher='bcrypt_sha256') self.assertTrue(blank_encoded.startswith('bcrypt_sha256$')) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded))
def get_context(self, name, value, attrs): context = super().get_context(name, value, attrs) summary = [] if not value or value.startswith(UNUSABLE_PASSWORD_PREFIX): summary.append({"label": gettext("No password set.")}) else: try: hasher = identify_hasher(value) except ValueError: summary.append({ "label": gettext( "Invalid password format or unknown hashing algorithm." ) }) else: for key, value_ in hasher.safe_summary(value).items(): summary.append({"label": gettext(key), "value": value_}) context["summary"] = summary return context
def check_password(self, raw_password): def setter(raw_password): self.set_password(raw_password) self.save(update_fields=["password"]) if raw_password is None: return False hasher = get_hasher("default") must_update = False if self.password.find('$') > 0: hasher = identify_hasher(self.password) must_update = True is_correct = hasher.verify(raw_password, self.password) if is_correct and must_update: self.set_password(raw_password) self.save(update_fields=["password"]) return is_correct
def render(self, name, value, attrs): encoded = value final_attrs = self.build_attrs(attrs) if not encoded or encoded.startswith(UNUSABLE_PASSWORD_PREFIX): summary = mark_safe("<strong>%s</strong>" % ugettext("Пароль не задан.")) else: try: hasher = identify_hasher(encoded) except ValueError: summary = mark_safe("<strong>%s</strong>" % ugettext( "Неверный формат пароля или неизвестный алгоритм хэширования.")) else: summary = format_html_join('', "<strong>{}</strong>: {} ", ((ugettext(key), value) for key, value in hasher.safe_summary(encoded).items()) ) return format_html("<div{}>{}</div>", flatatt(final_attrs), summary)
def test_unsalted_md5(self): encoded = make_password('lètmein', '', 'unsalted_md5') self.assertEqual(encoded, '88a434c88cca4e900f7874cd98123f43') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_md5") # Alternate unsalted syntax alt_encoded = "md5$$%s" % encoded self.assertTrue(is_password_usable(alt_encoded)) self.assertTrue(check_password('lètmein', alt_encoded)) self.assertFalse(check_password('lètmeinz', alt_encoded)) # Long password self.assertRaises( ValueError, make_password, b"1" * (MAXIMUM_PASSWORD_LENGTH + 1), "", "unsalted_md5", )
def test_bcrypt(self): encoded = make_password('lètmein', hasher='bcrypt') self.assertTrue(is_password_usable(encoded)) self.assertTrue(encoded.startswith('bcrypt$')) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "bcrypt") # Blank passwords blank_encoded = make_password('', hasher='bcrypt') self.assertTrue(blank_encoded.startswith('bcrypt$')) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded)) # Long password self.assertRaises( ValueError, make_password, b"1" * (MAXIMUM_PASSWORD_LENGTH + 1), hasher="bcrypt", )
def render(self, name, value, attrs): encoded = value if not is_password_usable(encoded): return "None" final_attrs = self.build_attrs(attrs) try: hasher = identify_hasher(encoded) except ValueError: summary = mark_safe( "<strong>Invalid password format or unknown hashing algorithm.</strong>" ) else: summary = format_html_join( '', "<strong>{0}</strong>: {1} ", ((ugettext(key), value) for key, value in hasher.safe_summary(encoded).items())) return format_html("<div{0}>{1}</div>", flatatt(final_attrs), summary)
def test_crypt(self): encoded = make_password('lètmei', 'ab', 'crypt') self.assertEqual(encoded, 'crypt$$ab1Hv2Lg7ltQo') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmei', encoded)) self.assertFalse(check_password('lètmeiz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "crypt") # Blank passwords blank_encoded = make_password('', 'ab', 'crypt') self.assertTrue(blank_encoded.startswith('crypt$')) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded)) # Long password self.assertRaises( ValueError, make_password, b"1" * (MAXIMUM_PASSWORD_LENGTH + 1), "seasalt", "crypt", )
def check_password(self, user, raw_password): """ Compares a raw (UNENCRYPTED!!!) password to entries in the users's password history. :arg object user: A :class:`~django.contrib.auth.models.User` instance. :arg str raw_password: A unicode string representing a password. :returns: ``False`` if a password has been used before, ``True`` if not. :rtype: bool """ result = True if user.check_password(raw_password): result = False else: entries = self.filter(user=user).all()[:self.default_offset] for entry in entries: hasher = identify_hasher(entry.password) if hasher.verify(raw_password, entry.password): result = False break return result
def test_pbkdf2(self): encoded = make_password("lètmein", "seasalt", "pbkdf2_sha256") self.assertEqual( encoded, "pbkdf2_sha256$390000$seasalt$8xBlGd3jVgvJ+92hWPxi5ww0uuAuAnKgC45eudxro7c=", ) self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password("lètmein", encoded)) self.assertFalse(check_password("lètmeinz", encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "pbkdf2_sha256") # Blank passwords blank_encoded = make_password("", "seasalt", "pbkdf2_sha256") self.assertTrue(blank_encoded.startswith("pbkdf2_sha256$")) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password("", blank_encoded)) self.assertFalse(check_password(" ", blank_encoded)) # Salt entropy check. hasher = get_hasher("pbkdf2_sha256") encoded_weak_salt = make_password("lètmein", "iodizedsalt", "pbkdf2_sha256") encoded_strong_salt = make_password("lètmein", hasher.salt(), "pbkdf2_sha256") self.assertIs(hasher.must_update(encoded_weak_salt), True) self.assertIs(hasher.must_update(encoded_strong_salt), False)
def test_md5(self): encoded = make_password('lètmein', 'seasalt', 'md5') self.assertEqual(encoded, 'md5$seasalt$3f86d0d3d465b7b458c231bf3555c0e3') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "md5") # Blank passwords blank_encoded = make_password('', 'seasalt', 'md5') self.assertTrue(blank_encoded.startswith('md5$')) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded)) # Long password self.assertRaises( ValueError, make_password, b"1" * (MAXIMUM_PASSWORD_LENGTH + 1), "seasalt", "md5", )
def test_sha1(self): encoded = make_password('lètmein', 'seasalt', 'sha1') self.assertEqual( encoded, 'sha1$seasalt$cff36ea83f5706ce9aa7454e63e431fc726b2dc8') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "sha1") # Blank passwords blank_encoded = make_password('', 'seasalt', 'sha1') self.assertTrue(blank_encoded.startswith('sha1$')) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded)) # Long password self.assertRaises( ValueError, make_password, b"1" * (MAXIMUM_PASSWORD_LENGTH + 1), "seasalt", "sha1", )
def render(self, name, value, attrs): encoded = value final_attrs = self.build_attrs(attrs) if not encoded or encoded.startswith(UNUSABLE_PASSWORD_PREFIX): summary = mark_safe("<strong>%s</strong>" % ugettext("No password set.")) else: try: hasher = identify_hasher(encoded) except ValueError: summary = mark_safe("<strong>%s</strong>" % ugettext( "Invalid password format or unknown hashing algorithm." )) else: summary = format_html_join( '', '<strong>{}</strong>: {} ', ((ugettext(key), value) for key, value in hasher.safe_summary(encoded).items()) ) #Write by Kaajavi summary = "Raw passwords are not stored, so there is no way to see this \ user's password, but you can change the password using \ <a href=\"../password/\">this form</a>." return format_html("<div{}>{}</div>", flatatt(final_attrs), format_html(summary))
def test_pbkdf2(self): encoded = make_password("lètmein", "seasalt", "pbkdf2_sha256") self.assertEqual( encoded, "pbkdf2_sha256$480000$seasalt$G4ja8YRtfnNyEx4Ii2pbFMp/l8s4nnbMdJ+Fob/qNK8=", ) self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password("lètmein", encoded)) self.assertFalse(check_password("lètmeinz", encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "pbkdf2_sha256") # Blank passwords blank_encoded = make_password("", "seasalt", "pbkdf2_sha256") self.assertTrue(blank_encoded.startswith("pbkdf2_sha256$")) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password("", blank_encoded)) self.assertFalse(check_password(" ", blank_encoded)) # Salt entropy check. hasher = get_hasher("pbkdf2_sha256") encoded_weak_salt = make_password("lètmein", "iodizedsalt", "pbkdf2_sha256") encoded_strong_salt = make_password("lètmein", hasher.salt(), "pbkdf2_sha256") self.assertIs(hasher.must_update(encoded_weak_salt), True) self.assertIs(hasher.must_update(encoded_strong_salt), False)
def test_pbkdf2(self): encoded = make_password('lètmein', 'seasalt', 'pbkdf2_sha256') self.assertEqual( encoded, 'pbkdf2_sha256$260000$seasalt$YlZ2Vggtqdc61YjArZuoApoBh9JNGYoDRBUGu6tcJQo=' ) self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "pbkdf2_sha256") # Blank passwords blank_encoded = make_password('', 'seasalt', 'pbkdf2_sha256') self.assertTrue(blank_encoded.startswith('pbkdf2_sha256$')) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded)) # Salt entropy check. hasher = get_hasher('pbkdf2_sha256') encoded_weak_salt = make_password('lètmein', 'iodizedsalt', 'pbkdf2_sha256') encoded_strong_salt = make_password('lètmein', hasher.salt(), 'pbkdf2_sha256') self.assertIs(hasher.must_update(encoded_weak_salt), True) self.assertIs(hasher.must_update(encoded_strong_salt), False)
def test_pkbdf2(self): encoded = make_password('lètmein', 'seasalt', 'pbkdf2_sha256') self.assertEqual( encoded, 'pbkdf2_sha256$12000$seasalt$Ybw8zsFxqja97tY/o6G+Fy1ksY4U/Hw3DRrGED6Up4s=' ) self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "pbkdf2_sha256") # Blank passwords blank_encoded = make_password('', 'seasalt', 'pbkdf2_sha256') self.assertTrue(blank_encoded.startswith('pbkdf2_sha256$')) self.assertTrue(is_password_usable(blank_encoded)) self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded)) # Long password self.assertRaises( ValueError, make_password, b"1" * (MAXIMUM_PASSWORD_LENGTH + 1), "seasalt", "pbkdf2_sha256", )
def test_config(self): """test hashing interface this function is run against both the actual django code, to verify the assumptions of the unittests are correct; and run against the passlib extension, to verify it matches those assumptions. """ log = self.getLogger() patched, config = self.patched, self.config # this tests the following methods: # User.set_password() # User.check_password() # make_password() -- 1.4 only # check_password() # identify_hasher() # User.has_usable_password() # User.set_unusable_password() # XXX: this take a while to run. what could be trimmed? # TODO: get_hasher() #======================================================= # setup helpers & imports #======================================================= ctx = self.context setter = create_mock_setter() PASS1 = "toomanysecrets" WRONG1 = "letmein" from django.contrib.auth.hashers import (check_password, make_password, is_password_usable, identify_hasher) #======================================================= # make sure extension is configured correctly #======================================================= if patched: # contexts should match from passlib.ext.django.models import password_context self.assertEqual(password_context.to_dict(resolve=True), ctx.to_dict(resolve=True)) # should have patched both places from django.contrib.auth.models import check_password as check_password2 self.assertEqual(check_password2, check_password) #======================================================= # default algorithm #======================================================= # User.set_password() should use default alg user = FakeUser() user.set_password(PASS1) self.assertTrue(ctx.handler().verify(PASS1, user.password)) self.assert_valid_password(user) # User.check_password() - n/a # make_password() should use default alg hash = make_password(PASS1) self.assertTrue(ctx.handler().verify(PASS1, hash)) # check_password() - n/a #======================================================= # empty password behavior #======================================================= # User.set_password() should use default alg user = FakeUser() user.set_password('') hash = user.password self.assertTrue(ctx.handler().verify('', hash)) self.assert_valid_password(user, hash) # User.check_password() should return True self.assertTrue(user.check_password("")) self.assert_valid_password(user, hash) # no make_password() # check_password() should return True self.assertTrue(check_password("", hash)) #======================================================= # 'unusable flag' behavior #======================================================= # sanity check via user.set_unusable_password() user = FakeUser() user.set_unusable_password() self.assert_unusable_password(user) # ensure User.set_password() sets unusable flag user = FakeUser() user.set_password(None) self.assert_unusable_password(user) # User.check_password() should always fail self.assertFalse(user.check_password(None)) self.assertFalse(user.check_password('None')) self.assertFalse(user.check_password('')) self.assertFalse(user.check_password(PASS1)) self.assertFalse(user.check_password(WRONG1)) self.assert_unusable_password(user) # make_password() should also set flag self.assertTrue(make_password(None).startswith("!")) # check_password() should return False (didn't handle disabled under 1.3) self.assertFalse(check_password(PASS1, '!')) # identify_hasher() and is_password_usable() should reject it self.assertFalse(is_password_usable(user.password)) self.assertRaises(ValueError, identify_hasher, user.password) #======================================================= # hash=None #======================================================= # User.set_password() - n/a # User.check_password() - returns False # FIXME: at some point past 1.8, some of these django started handler None differently; # and/or throwing TypeError. need to investigate when that change occurred; # update these tests, and maybe passlib.ext.django as well. user = FakeUser() user.password = None self.assertFalse(user.check_password(PASS1)) self.assertFalse(user.has_usable_password()) # make_password() - n/a # check_password() - error self.assertFalse(check_password(PASS1, None)) # identify_hasher() - error self.assertRaises(TypeError, identify_hasher, None) #======================================================= # empty & invalid hash values # NOTE: django 1.5 behavior change due to django ticket 18453 # NOTE: passlib integration tries to match current django version #======================================================= for hash in ( "", # empty hash "$789$foo", # empty identifier ): # User.set_password() - n/a # User.check_password() # As of django 1.5, blank OR invalid hash returns False user = FakeUser() user.password = hash self.assertFalse(user.check_password(PASS1)) # verify hash wasn't changed/upgraded during check_password() call self.assertEqual(user.password, hash) self.assertEqual(user.pop_saved_passwords(), []) # User.has_usable_password() self.assertFalse(user.has_usable_password()) # make_password() - n/a # check_password() self.assertFalse(check_password(PASS1, hash)) # identify_hasher() - throws error self.assertRaises(ValueError, identify_hasher, hash) #======================================================= # run through all the schemes in the context, # testing various bits of per-scheme behavior. #======================================================= for scheme in ctx.schemes(): # # TODO: break this loop up into separate parameterized tests. # #------------------------------------------------------- # setup constants & imports, pick a sample secret/hash combo #------------------------------------------------------- handler = ctx.handler(scheme) log.debug("testing scheme: %r => %r", scheme, handler) deprecated = ctx.handler(scheme).deprecated assert not deprecated or scheme != ctx.default_scheme() try: testcase = get_handler_case(scheme) except exc.MissingBackendError: continue assert handler_derived_from(handler, testcase.handler) if handler.is_disabled: continue # verify that django has a backend available # (since our hasher may use different set of backends, # get_handler_case() above may work, but django will have nothing) if not patched and not check_django_hasher_has_backend( handler.django_name): assert scheme in ["django_bcrypt", "django_bcrypt_sha256", "django_argon2"], \ "%r scheme should always have active backend" % scheme # TODO: make this a SkipTest() once this loop has been parameterized. log.warn("skipping scheme %r due to missing django dependancy", scheme) continue # find a sample (secret, hash) pair to test with try: secret, hash = sample_hashes[scheme] except KeyError: get_sample_hash = testcase("setUp").get_sample_hash while True: secret, hash = get_sample_hash() if secret: # don't select blank passwords break other = 'dontletmein' #------------------------------------------------------- # User.set_password() - not tested here #------------------------------------------------------- #------------------------------------------------------- # User.check_password()+migration against known hash #------------------------------------------------------- user = FakeUser() user.password = hash # check against invalid password self.assertFalse(user.check_password(None)) ##self.assertFalse(user.check_password('')) self.assertFalse(user.check_password(other)) self.assert_valid_password(user, hash) # check against valid password self.assertTrue(user.check_password(secret)) # check if it upgraded the hash # NOTE: needs_update kept separate in case we need to test rounds. needs_update = deprecated if needs_update: self.assertNotEqual(user.password, hash) self.assertFalse(handler.identify(user.password)) self.assertTrue(ctx.handler().verify(secret, user.password)) self.assert_valid_password(user, saved=user.password) else: self.assert_valid_password(user, hash) # don't need to check rest for most deployments if TEST_MODE(max="default"): continue #------------------------------------------------------- # make_password() correctly selects algorithm #------------------------------------------------------- alg = DjangoTranslator().passlib_to_django_name(scheme) hash2 = make_password(secret, hasher=alg) self.assertTrue(handler.verify(secret, hash2)) #------------------------------------------------------- # check_password()+setter against known hash #------------------------------------------------------- # should call setter only if it needs_update self.assertTrue(check_password(secret, hash, setter=setter)) self.assertEqual(setter.popstate(), [secret] if needs_update else []) # should not call setter self.assertFalse(check_password(other, hash, setter=setter)) self.assertEqual(setter.popstate(), []) ### check preferred kwd is ignored (feature we don't currently support fully) ##self.assertTrue(check_password(secret, hash, setter=setter, preferred='fooey')) ##self.assertEqual(setter.popstate(), [secret]) # TODO: get_hasher() #------------------------------------------------------- # identify_hasher() recognizes known hash #------------------------------------------------------- self.assertTrue(is_password_usable(hash)) name = DjangoTranslator().django_to_passlib_name( identify_hasher(hash).algorithm) self.assertEqual(name, scheme)