Skip to content

Commit

Permalink
Fix coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
jamadden committed Aug 23, 2017
1 parent 9e23db9 commit 895002c
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 105 deletions.
139 changes: 69 additions & 70 deletions src/zope/password/legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,98 +32,97 @@
PY2 = sys.version_info[0] == 2


if crypt is not None:
@implementer(IMatchingPasswordManager)
class CryptPasswordManager(object):
"""Crypt password manager.
@implementer(IMatchingPasswordManager)
class CryptPasswordManager(object):
"""Crypt password manager.
Implements a UNIX crypt(3) hashing scheme. Note that crypt is
considered far inferior to more modern schemes such as SSHA hashing,
and only uses the first 8 characters of a password.
Implements a UNIX crypt(3) hashing scheme. Note that crypt is
considered far inferior to more modern schemes such as SSHA hashing,
and only uses the first 8 characters of a password.
>>> from zope.interface.verify import verifyObject
>>> from zope.password.interfaces import IMatchingPasswordManager
>>> from zope.password.legacy import CryptPasswordManager
>>> from zope.interface.verify import verifyObject
>>> from zope.password.interfaces import IMatchingPasswordManager
>>> from zope.password.legacy import CryptPasswordManager
>>> manager = CryptPasswordManager()
>>> verifyObject(IMatchingPasswordManager, manager)
True
>>> manager = CryptPasswordManager()
>>> verifyObject(IMatchingPasswordManager, manager)
True
>>> password = u"right \N{CYRILLIC CAPITAL LETTER A}"
>>> encoded = manager.encodePassword(password, salt="..")
>>> encoded
'{CRYPT}..I1I8wps4Na2'
>>> manager.match(encoded)
True
>>> manager.checkPassword(encoded, password)
True
>>> password = u"right \N{CYRILLIC CAPITAL LETTER A}"
>>> encoded = manager.encodePassword(password, salt="..")
>>> encoded
'{CRYPT}..I1I8wps4Na2'
>>> manager.match(encoded)
True
>>> manager.checkPassword(encoded, password)
True
Note that this object fails to return bytes from the ``encodePassword``
function on Python 3:
Note that this object fails to return bytes from the ``encodePassword``
function on Python 3:
>>> isinstance(encoded, str)
True
>>> isinstance(encoded, str)
True
Unfortunately, crypt only looks at the first 8 characters, so matching
against an 8 character password plus suffix always matches. Our test
password (including utf-8 encoding) is exactly 8 characters long, and
thus affixing 'wrong' to it tests as a correct password:
Unfortunately, crypt only looks at the first 8 characters, so matching
against an 8 character password plus suffix always matches. Our test
password (including utf-8 encoding) is exactly 8 characters long, and
thus affixing 'wrong' to it tests as a correct password:
>>> manager.checkPassword(encoded, password + u"wrong")
True
>>> manager.checkPassword(encoded, password + u"wrong")
True
Using a completely different password is rejected as expected:
Using a completely different password is rejected as expected:
>>> manager.checkPassword(encoded, 'completely wrong')
False
>>> manager.checkPassword(encoded, 'completely wrong')
False
Using the `openssl passwd` command-line utility to encode ``secret``,
we get ``erz50QD3gv4Dw`` as seeded hash.
Using the `openssl passwd` command-line utility to encode ``secret``,
we get ``erz50QD3gv4Dw`` as seeded hash.
Our password manager generates the same value when seeded with the
same salt, so we can be sure, our output is compatible with
standard LDAP tools that also use crypt:
Our password manager generates the same value when seeded with the
same salt, so we can be sure, our output is compatible with
standard LDAP tools that also use crypt:
>>> salt = 'er'
>>> password = 'secret'
>>> encoded = manager.encodePassword(password, salt)
>>> encoded
'{CRYPT}erz50QD3gv4Dw'
>>> salt = 'er'
>>> password = 'secret'
>>> encoded = manager.encodePassword(password, salt)
>>> encoded
'{CRYPT}erz50QD3gv4Dw'
>>> manager.checkPassword(encoded, password)
True
>>> manager.checkPassword(encoded, password + u"wrong")
False
>>> manager.checkPassword(encoded, password)
True
>>> manager.checkPassword(encoded, password + u"wrong")
False
>>> manager.encodePassword(password) != manager.encodePassword(password)
True
>>> manager.encodePassword(password) != manager.encodePassword(password)
True
The manager only claims to implement CRYPT encodings, anything not
starting with the string {CRYPT} returns False:
The manager only claims to implement CRYPT encodings, anything not
starting with the string {CRYPT} returns False:
>>> manager.match('{MD5}someotherhash')
False
>>> manager.match('{MD5}someotherhash')
False
"""
"""


def encodePassword(self, password, salt=None):
if salt is None:
choices = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789./")
salt = choice(choices) + choice(choices)
if PY2:
# Py3: Python 2 can only handle ASCII for crypt.
password = _encoder(password)[0]
return '{CRYPT}%s' % crypt(password, salt)
def encodePassword(self, password, salt=None):
if salt is None:
choices = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789./")
salt = choice(choices) + choice(choices)
if PY2:
# Py3: Python 2 can only handle ASCII for crypt.
password = _encoder(password)[0]
return '{CRYPT}%s' % crypt(password, salt)

def checkPassword(self, encoded_password, password):
return encoded_password == self.encodePassword(password,
encoded_password[7:9])
def checkPassword(self, encoded_password, password):
return encoded_password == self.encodePassword(password,
encoded_password[7:9])

def match(self, encoded_password):
return encoded_password.startswith('{CRYPT}')
def match(self, encoded_password):
return encoded_password.startswith('{CRYPT}')


@implementer(IMatchingPasswordManager)
Expand Down
39 changes: 7 additions & 32 deletions src/zope/password/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,15 @@
"""
__docformat__ = "reStructuredText"

from zope.component import provideUtility
from zope.schema.interfaces import IVocabularyFactory

from zope.password.interfaces import IMatchingPasswordManager
from zope.password.password import PlainTextPasswordManager
from zope.password.password import MD5PasswordManager
from zope.password.password import SMD5PasswordManager
from zope.password.password import SHA1PasswordManager
from zope.password.password import SSHAPasswordManager
from zope.password.legacy import MySQLPasswordManager
from zope.password.vocabulary import PasswordManagerNamesVocabulary

try:
from zope.password.legacy import CryptPasswordManager
except ImportError: # pragma: no cover
CryptPasswordManager = None

import zope.password
from zope.configuration import xmlconfig

def setUpPasswordManagers():
"""Helper function for setting up password manager utilities for tests
>>> from zope.component import getUtility
>>> from zope.password.interfaces import IMatchingPasswordManager
>>> from zope.schema.interfaces import IVocabularyFactory
>>> setUpPasswordManagers()
>>> getUtility(IMatchingPasswordManager, 'Plain Text')
Expand All @@ -58,8 +45,8 @@ def setUpPasswordManagers():
... CryptPasswordManager = None
... True
... else:
... from zope.password.legacy import CryptPasswordManager as cpm
... getUtility(IMatchingPasswordManager, 'Crypt') is cpm
... from zope.password.legacy import CryptPasswordManager
... getUtility(IMatchingPasswordManager, 'Crypt') is not None
True
>>> voc = getUtility(IVocabularyFactory, 'Password Manager Names')
Expand All @@ -83,16 +70,4 @@ def setUpPasswordManagers():
True
"""
provideUtility(PlainTextPasswordManager(), IMatchingPasswordManager,
'Plain Text')
provideUtility(SSHAPasswordManager(), IMatchingPasswordManager, 'SSHA')
provideUtility(MD5PasswordManager(), IMatchingPasswordManager, 'MD5')
provideUtility(SMD5PasswordManager(), IMatchingPasswordManager, 'SMD5')
provideUtility(SHA1PasswordManager(), IMatchingPasswordManager, 'SHA1')
provideUtility(MySQLPasswordManager(), IMatchingPasswordManager, 'MySQL')

if CryptPasswordManager is not None:
provideUtility(CryptPasswordManager, IMatchingPasswordManager, 'Crypt')

provideUtility(PasswordManagerNamesVocabulary,
IVocabularyFactory, 'Password Manager Names')
xmlconfig.file('configure.zcml', zope.password)
7 changes: 5 additions & 2 deletions src/zope/password/tests/test_password.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,15 @@ def test_crypt_utility_names(self):


def test_suite():
from zope.component.testing import setUp, tearDown
suite = unittest.TestSuite((
doctest.DocTestSuite('zope.password.password'),
doctest.DocTestSuite('zope.password.legacy'),
doctest.DocTestSuite(
'zope.password.testing',
optionflags=doctest.ELLIPSIS),
))
optionflags=doctest.ELLIPSIS,
setUp=setUp,
tearDown=tearDown),
))
suite.addTests(unittest.defaultTestLoader.loadTestsFromName(__name__))
return suite
5 changes: 4 additions & 1 deletion src/zope/password/tests/test_zpasswd.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ def setUp(self):
suffix=".zcml",
delete=False) as f:
f.write(
b'<configure xmlns="http://namespaces.zope.org/zope"/>\n')
b"""<configure xmlns="http://namespaces.zope.org/zope">
<include file="configure.zcml" package="zope.password" />
</configure>
""")
self.config = f.name
self.addCleanup(os.remove, f.name)

Expand Down

0 comments on commit 895002c

Please sign in to comment.