r46908 - Merge conch-checkers-py3-8225: Port twisted.conch.checkers to Python 3

hawkowl-TA+aISz0psMTMxyoc4vAAJOcrHinNvQL0E9HWUfgJXw@public.gmane.org
Newsgroups gmane.comp.python.twisted.commits
Message-ID <[email protected]>
Author: hawkowl
Date: Thu Mar  3 23:41:01 2016
New Revision: 46908

Added:
   trunk/twisted/conch/topfiles/8225.feature
Modified:
   trunk/twisted/conch/checkers.py
   trunk/twisted/conch/error.py
   trunk/twisted/conch/test/test_checkers.py
   trunk/twisted/python/compat.py
   trunk/twisted/python/dist3.py

Log:
Merge conch-checkers-py3-8225: Port twisted.conch.checkers to Python 3

Author: hawkowl
Reviewer: adiroiban
Fixes: #8225

Modified: trunk/twisted/conch/checkers.py
==============================================================================
--- trunk/twisted/conch/checkers.py	(original)
+++ trunk/twisted/conch/checkers.py	Thu Mar  3 23:41:01 2016
@@ -6,7 +6,12 @@
 Provide L{ICredentialsChecker} implementations to be used in Conch protocols.
 """
 
-import base64, binascii, errno
+from __future__ import absolute_import, division
+
+import sys
+import binascii
+import errno
+
 try:
     import pwd
 except ImportError:
@@ -15,26 +20,19 @@
     import crypt
 
 try:
-    # Python 2.5 got spwd to interface with shadow passwords
     import spwd
 except ImportError:
     spwd = None
-    try:
-        import shadow
-    except ImportError:
-        shadow = None
-else:
-    shadow = None
 
 from zope.interface import providedBy, implementer, Interface
 
-
 from twisted.conch import error
 from twisted.conch.ssh import keys
 from twisted.cred.checkers import ICredentialsChecker
 from twisted.cred.credentials import IUsernamePassword, ISSHPrivateKey
 from twisted.cred.error import UnauthorizedLogin, UnhandledCredentials
 from twisted.internet import defer
+from twisted.python.compat import _keys, _PY3, _b64decodebytes
 from twisted.python import failure, reflect, log
 from twisted.python.deprecate import deprecatedModuleAttribute
 from twisted.python.util import runAsEffectiveUser
@@ -44,6 +42,16 @@
 
 
 def verifyCryptedPassword(crypted, pw):
+    """
+    Check that the password, when crypted, matches the stored crypted password.
+
+    @param crypted: The stored crypted password.
+    @type crypted: L{str}
+    @param pw: The password the user has given.
+    @type pw: L{str}
+
+    @rtype: L{bool}
+    """
     return crypt.crypt(pw, crypted) == crypted
 
 
@@ -55,6 +63,7 @@
 
     @param username: the username of the user to return the passwd database
         information for.
+    @type username: L{str}
     """
     if pwd is None:
         return None
@@ -64,16 +73,15 @@
 
 def _shadowGetByName(username):
     """
-    Look up a user in the /etc/shadow database using the spwd or shadow
-    modules.  If neither module is available, return None.
+    Look up a user in the /etc/shadow database using the spwd module. If it is
+    not available, return C{None}.
 
     @param username: the username of the user to return the shadow database
         information for.
+    @type username: L{str}
     """
     if spwd is not None:
         f = spwd.getspnam
-    elif shadow is not None:
-        f = shadow.getspnam
     else:
         return None
     return runAsEffectiveUser(0, 0, f, username)
@@ -87,8 +95,8 @@
     databases of a compatible format.
 
     @ivar _getByNameFunctions: a C{list} of functions which are called in order
-        to valid a user.  The default value is such that the /etc/passwd
-        database will be tried first, followed by the /etc/shadow database.
+        to valid a user.  The default value is such that the C{/etc/passwd}
+        database will be tried first, followed by the C{/etc/shadow} database.
     """
     credentialInterfaces = IUsernamePassword,
 
@@ -99,9 +107,18 @@
 
 
     def requestAvatarId(self, credentials):
+        # We get bytes, but the Py3 pwd module uses str. So attempt to decode
+        # it using the same method that CPython does for the file on disk.
+        if _PY3:
+            username = credentials.username.decode(sys.getfilesystemencoding())
+            password = credentials.password.decode(sys.getfilesystemencoding())
+        else:
+            username = credentials.username
+            password = credentials.password
+
         for func in self._getByNameFunctions:
             try:
-                pwnam = func(credentials.username)
+                pwnam = func(username)
             except KeyError:
                 return defer.fail(UnauthorizedLogin("invalid username"))
             else:
@@ -109,7 +126,8 @@
                     crypted = pwnam[1]
                     if crypted == '':
                         continue
-                    if verifyCryptedPassword(crypted, credentials.password):
+
+                    if verifyCryptedPassword(crypted, password):
                         return defer.succeed(credentials.username)
         # fallback
         return defer.fail(UnauthorizedLogin("unable to verify password"))
@@ -211,7 +229,7 @@
                 if len(l2) < 2:
                     continue
                 try:
-                    if base64.decodestring(l2[1]) == credentials.blob:
+                    if _b64decodebytes(l2[1]) == credentials.blob:
                         return True
                 except binascii.Error:
                     continue
@@ -243,7 +261,7 @@
         self.successfulCredentials = {}
 
     def get_credentialInterfaces(self):
-        return self.checkers.keys()
+        return _keys(self.checkers)
 
     credentialInterfaces = property(get_credentialInterfaces)
 
@@ -354,7 +372,7 @@
     """
     for line in fileobj:
         line = line.strip()
-        if line and not line.startswith('#'):  # for comments
+        if line and not line.startswith(b'#'):  # for comments
             try:
                 yield parseKey(line)
             except keys.BadKeyError as e:
@@ -553,7 +571,7 @@
             was any error verifying the signature.
 
         @return: The user's username, if authentication was successful
-        @rtype: C{str}
+        @rtype: L{bytes}
         """
         try:
             if pubKey.verify(credentials.signature, credentials.sigData):

Modified: trunk/twisted/conch/error.py
==============================================================================
--- trunk/twisted/conch/error.py	(original)
+++ trunk/twisted/conch/error.py	Thu Mar  3 23:41:01 2016
@@ -7,8 +7,9 @@
 Maintainer: Paul Swartz
 """
 
-from twisted.cred.error import UnauthorizedLogin
+from __future__ import absolute_import, division
 
+from twisted.cred.error import UnauthorizedLogin
 
 
 class ConchError(Exception):

Modified: trunk/twisted/conch/test/test_checkers.py
==============================================================================
--- trunk/twisted/conch/test/test_checkers.py	(original)
+++ trunk/twisted/conch/test/test_checkers.py	Thu Mar  3 23:41:01 2016
@@ -5,6 +5,8 @@
 Tests for L{twisted.conch.checkers}.
 """
 
+from __future__ import absolute_import, division
+
 try:
     import crypt
 except ImportError:
@@ -12,13 +14,15 @@
 else:
     cryptSkip = None
 
-import os, base64
+import os
+
 from collections import namedtuple
-from io import StringIO
+from io import BytesIO
 
 from zope.interface.verify import verifyObject
 
 from twisted.python import util
+from twisted.python.compat import _b64encodebytes
 from twisted.python.failure import Failure
 from twisted.python.reflect import requireModule
 from twisted.trial.unittest import TestCase
@@ -30,6 +34,7 @@
 from twisted.python.fakepwd import UserDatabase, ShadowDatabase
 from twisted.test.test_process import MockOS
 
+
 if requireModule('cryptography') and requireModule('pyasn1'):
     dependencySkip = None
     from twisted.conch.ssh import keys
@@ -140,32 +145,9 @@
 
     def test_shadowGetByNameWithoutSpwd(self):
         """
-        L{_shadowGetByName} uses the C{shadow} module to return a tuple of items
-        from the UNIX /etc/shadow database if the C{spwd} module is not present
-        and the C{shadow} module is.
-        """
-        userdb = ShadowDatabase()
-        userdb.addUser('bob', 'passphrase', 1, 2, 3, 4, 5, 6, 7)
-        self.patch(checkers, 'spwd', None)
-        self.patch(checkers, 'shadow', userdb)
-        self.patch(util, 'os', self.mockos)
-
-        self.mockos.euid = 2345
-        self.mockos.egid = 1234
-
-        self.assertEqual(
-            checkers._shadowGetByName('bob'), userdb.getspnam('bob'))
-        self.assertEqual(self.mockos.seteuidCalls, [0, 2345])
-        self.assertEqual(self.mockos.setegidCalls, [0, 1234])
-
-
-    def test_shadowGetByNameWithoutEither(self):
-        """
-        L{_shadowGetByName} returns C{None} if neither C{spwd} nor C{shadow} is
-        present.
+        L{_shadowGetByName} returns C{None} if C{spwd} is not present.
         """
         self.patch(checkers, 'spwd', None)
-        self.patch(checkers, 'shadow', None)
 
         self.assertIs(checkers._shadowGetByName('bob'), None)
         self.assertEqual(self.mockos.seteuidCalls, [])
@@ -181,9 +163,10 @@
 
     def setUp(self):
         self.checker = checkers.SSHPublicKeyDatabase()
-        self.key1 = base64.encodestring("foobar")
-        self.key2 = base64.encodestring("eggspam")
-        self.content = "t1 %s foo\nt2 %s egg\n" % (self.key1, self.key2)
+        self.key1 = _b64encodebytes(b"foobar")
+        self.key2 = _b64encodebytes(b"eggspam")
+        self.content = (b"t1 " + self.key1 + b" foo\nt2 " + self.key2 +
+                        b" egg\n")
 
         self.mockos = MockOS()
         self.mockos.path = FilePath(self.mktemp())
@@ -194,8 +177,8 @@
 
         userdb = UserDatabase()
         userdb.addUser(
-            'user', 'password', 1, 2, 'first last',
-            self.mockos.path.path, '/bin/shell')
+            b'user', b'password', 1, 2, b'first last',
+            self.mockos.path.path, b'/bin/shell')
         self.checker._userdb = userdb
 
 
@@ -218,12 +201,12 @@
 
     def _testCheckKey(self, filename):
         self.sshDir.child(filename).setContent(self.content)
-        user = UsernamePassword("user", "password")
-        user.blob = "foobar"
+        user = UsernamePassword(b"user", b"password")
+        user.blob = b"foobar"
         self.assertTrue(self.checker.checkKey(user))
-        user.blob = "eggspam"
+        user.blob = b"eggspam"
         self.assertTrue(self.checker.checkKey(user))
-        user.blob = "notallowed"
+        user.blob = b"notallowed"
         self.assertFalse(self.checker.checkKey(user))
 
 
@@ -255,19 +238,19 @@
         keyFile = self.sshDir.child("authorized_keys")
         keyFile.setContent(self.content)
         # Fake permission error by changing the mode
-        keyFile.chmod(0000)
-        self.addCleanup(keyFile.chmod, 0777)
+        keyFile.chmod(0o000)
+        self.addCleanup(keyFile.chmod, 0o777)
         # And restore the right mode when seteuid is called
         savedSeteuid = self.mockos.seteuid
         def seteuid(euid):
-            keyFile.chmod(0777)
+            keyFile.chmod(0o777)
             return savedSeteuid(euid)
         self.mockos.euid = 2345
         self.mockos.egid = 1234
         self.patch(self.mockos, "seteuid", seteuid)
         self.patch(util, 'os', self.mockos)
-        user = UsernamePassword("user", "password")
-        user.blob = "foobar"
+        user = UsernamePassword(b"user", b"password")
+        user.blob = b"foobar"
         self.assertTrue(self.checker.checkKey(user))
         self.assertEqual(self.mockos.seteuidCalls, [0, 1, 0, 2345])
         self.assertEqual(self.mockos.setegidCalls, [2, 1234])
@@ -282,11 +265,11 @@
             return True
         self.patch(self.checker, 'checkKey', _checkKey)
         credentials = SSHPrivateKey(
-            'test', 'ssh-rsa', keydata.publicRSA_openssh, 'foo',
-            keys.Key.fromString(keydata.privateRSA_openssh).sign('foo'))
+            b'test', b'ssh-rsa', keydata.publicRSA_openssh, b'foo',
+            keys.Key.fromString(keydata.privateRSA_openssh).sign(b'foo'))
         d = self.checker.requestAvatarId(credentials)
         def _verify(avatarId):
-            self.assertEqual(avatarId, 'test')
+            self.assertEqual(avatarId, b'test')
         return d.addCallback(_verify)
 
 
@@ -301,7 +284,7 @@
             return True
         self.patch(self.checker, 'checkKey', _checkKey)
         credentials = SSHPrivateKey(
-            'test', 'ssh-rsa', keydata.publicRSA_openssh, None, None)
+            b'test', b'ssh-rsa', keydata.publicRSA_openssh, None, None)
         d = self.checker.requestAvatarId(credentials)
         return self.assertFailure(d, ValidPublicKey)
 
@@ -328,8 +311,8 @@
             return True
         self.patch(self.checker, 'checkKey', _checkKey)
         credentials = SSHPrivateKey(
-            'test', 'ssh-rsa', keydata.publicRSA_openssh, 'foo',
-            keys.Key.fromString(keydata.privateDSA_openssh).sign('foo'))
+            b'test', b'ssh-rsa', keydata.publicRSA_openssh, b'foo',
+            keys.Key.fromString(keydata.privateDSA_openssh).sign(b'foo'))
         d = self.checker.requestAvatarId(credentials)
         return self.assertFailure(d, UnauthorizedLogin)
 
@@ -342,7 +325,7 @@
         def _checkKey(ignored):
             return True
         self.patch(self.checker, 'checkKey', _checkKey)
-        credentials = SSHPrivateKey('test', None, 'blob', 'sigData', 'sig')
+        credentials = SSHPrivateKey(b'test', None, b'blob', b'sigData', b'sig')
         d = self.checker.requestAvatarId(credentials)
         def _verifyLoggedException(failure):
             errors = self.flushLoggedErrors(keys.BadKeyError)
@@ -396,11 +379,11 @@
         """
         checker = checkers.SSHProtocolChecker()
         passwordDatabase = InMemoryUsernamePasswordDatabaseDontUse()
-        passwordDatabase.addUser('test', 'test')
+        passwordDatabase.addUser(b'test', b'test')
         checker.registerChecker(passwordDatabase)
-        d = checker.requestAvatarId(UsernamePassword('test', 'test'))
+        d = checker.requestAvatarId(UsernamePassword(b'test', b'test'))
         def _callback(avatarId):
-            self.assertEqual(avatarId, 'test')
+            self.assertEqual(avatarId, b'test')
         return d.addCallback(_callback)
 
 
@@ -416,9 +399,9 @@
         self.patch(checker, 'areDone', _areDone)
 
         passwordDatabase = InMemoryUsernamePasswordDatabaseDontUse()
-        passwordDatabase.addUser('test', 'test')
+        passwordDatabase.addUser(b'test', b'test')
         checker.registerChecker(passwordDatabase)
-        d = checker.requestAvatarId(UsernamePassword('test', 'test'))
+        d = checker.requestAvatarId(UsernamePassword(b'test', b'test'))
         return self.assertFailure(d, NotEnoughAuthentication)
 
 
@@ -428,7 +411,7 @@
         L{SSHProtocolChecker} should raise L{UnhandledCredentials}.
         """
         checker = checkers.SSHProtocolChecker()
-        d = checker.requestAvatarId(UsernamePassword('test', 'test'))
+        d = checker.requestAvatarId(UsernamePassword(b'test', b'test'))
         return self.assertFailure(d, UnhandledCredentials)
 
 
@@ -497,12 +480,12 @@
         mockos.euid = 2345
         mockos.egid = 1234
 
-        cred = UsernamePassword("alice", "password")
-        self.assertLoggedIn(checker.requestAvatarId(cred), 'alice')
+        cred = UsernamePassword(b"alice", b"password")
+        self.assertLoggedIn(checker.requestAvatarId(cred), b'alice')
         self.assertEqual(mockos.seteuidCalls, [])
         self.assertEqual(mockos.setegidCalls, [])
-        cred.username = "bob"
-        self.assertLoggedIn(checker.requestAvatarId(cred), 'bob')
+        cred.username = b"bob"
+        self.assertLoggedIn(checker.requestAvatarId(cred), b'bob')
         self.assertEqual(mockos.seteuidCalls, [0, 2345])
         self.assertEqual(mockos.setegidCalls, [0, 1234])
 
@@ -534,8 +517,8 @@
         userdb.addUser('anybody', password, 1, 2, 'foo', '/bar', '/bin/sh')
         checker = checkers.UNIXPasswordDatabase([userdb.getpwnam])
         self.assertLoggedIn(
-            checker.requestAvatarId(UsernamePassword('anybody', 'secret')),
-            'anybody')
+            checker.requestAvatarId(UsernamePassword(b'anybody', b'secret')),
+            b'anybody')
 
 
     def test_verifyPassword(self):
@@ -550,8 +533,8 @@
             return [username, username]
         self.patch(checkers, 'verifyCryptedPassword', verifyCryptedPassword)
         checker = checkers.UNIXPasswordDatabase([getpwnam])
-        credential = UsernamePassword('username', 'username')
-        self.assertLoggedIn(checker.requestAvatarId(credential), 'username')
+        credential = UsernamePassword(b'username', b'username')
+        self.assertLoggedIn(checker.requestAvatarId(credential), b'username')
 
 
     def test_failOnKeyError(self):
@@ -562,7 +545,7 @@
         def getpwnam(username):
             raise KeyError(username)
         checker = checkers.UNIXPasswordDatabase([getpwnam])
-        credential = UsernamePassword('username', 'username')
+        credential = UsernamePassword(b'username', b'username')
         self.assertUnauthorizedLogin(checker.requestAvatarId(credential))
 
 
@@ -577,7 +560,7 @@
             return [username, username]
         self.patch(checkers, 'verifyCryptedPassword', verifyCryptedPassword)
         checker = checkers.UNIXPasswordDatabase([getpwnam])
-        credential = UsernamePassword('username', 'username')
+        credential = UsernamePassword(b'username', b'username')
         self.assertUnauthorizedLogin(checker.requestAvatarId(credential))
 
 
@@ -596,8 +579,8 @@
             return [username, username]
         self.patch(checkers, 'verifyCryptedPassword', verifyCryptedPassword)
         checker = checkers.UNIXPasswordDatabase([getpwnam1, getpwnam2])
-        credential = UsernamePassword('username', 'username')
-        self.assertLoggedIn(checker.requestAvatarId(credential), 'username')
+        credential = UsernamePassword(b'username', b'username')
+        self.assertLoggedIn(checker.requestAvatarId(credential), b'username')
 
 
     def test_failOnSpecial(self):
@@ -612,13 +595,13 @@
         self.patch(checkers, 'pwd', pwd)
 
         checker = checkers.UNIXPasswordDatabase([checkers._pwdGetByName])
-        cred = UsernamePassword('alice', '')
+        cred = UsernamePassword(b'alice', b'')
         self.assertUnauthorizedLogin(checker.requestAvatarId(cred))
 
-        cred = UsernamePassword('bob', 'x')
+        cred = UsernamePassword(b'bob', b'x')
         self.assertUnauthorizedLogin(checker.requestAvatarId(cred))
 
-        cred = UsernamePassword('carol', '*')
+        cred = UsernamePassword(b'carol', b'*')
         self.assertUnauthorizedLogin(checker.requestAvatarId(cred))
 
 
@@ -635,12 +618,12 @@
         L{checkers.readAuthorizedKeyFile} does not attempt to turn comments
         into keys
         """
-        fileobj = StringIO(u'# this comment is ignored\n'
-                           u'this is not\n'
-                           u'# this is again\n'
-                           u'and this is not')
+        fileobj = BytesIO(b'# this comment is ignored\n'
+                          b'this is not\n'
+                          b'# this is again\n'
+                          b'and this is not')
         result = checkers.readAuthorizedKeyFile(fileobj, lambda x: x)
-        self.assertEqual(['this is not', 'and this is not'], list(result))
+        self.assertEqual([b'this is not', b'and this is not'], list(result))
 
 
     def test_ignoresLeadingWhitespaceAndEmptyLines(self):
@@ -648,12 +631,12 @@
         L{checkers.readAuthorizedKeyFile} ignores leading whitespace in
         lines, as well as empty lines
         """
-        fileobj = StringIO(u"""
+        fileobj = BytesIO(b"""
                            # ignore
                            not ignored
                            """)
         result = checkers.readAuthorizedKeyFile(fileobj, parseKey=lambda x: x)
-        self.assertEqual(['not ignored'], list(result))
+        self.assertEqual([b'not ignored'], list(result))
 
 
     def test_ignoresUnparsableKeys(self):
@@ -663,14 +646,14 @@
         L{twisted.conch.ssh.keys.BadKeyError}), but rather just keeps going
         """
         def failOnSome(line):
-            if line.startswith('f'):
+            if line.startswith(b'f'):
                 raise keys.BadKeyError('failed to parse')
             return line
 
-        fileobj = StringIO(u'failed key\ngood key')
+        fileobj = BytesIO(b'failed key\ngood key')
         result = checkers.readAuthorizedKeyFile(fileobj,
                                                 parseKey=failOnSome)
-        self.assertEqual(['good key'], list(result))
+        self.assertEqual([b'good key'], list(result))
 
 
 
@@ -686,7 +669,7 @@
         L{checkers.InMemorySSHKeyDB} implements
         L{checkers.IAuthorizedKeysDB}
         """
-        keydb = checkers.InMemorySSHKeyDB({'alice': ['key']})
+        keydb = checkers.InMemorySSHKeyDB({b'alice': [b'key']})
         verifyObject(checkers.IAuthorizedKeysDB, keydb)
 
 
@@ -696,8 +679,8 @@
         L{checkers.InMemorySSHKeyDB}, an empty iterator is returned
         by L{checkers.InMemorySSHKeyDB.getAuthorizedKeys}
         """
-        keydb = checkers.InMemorySSHKeyDB({'alice': ['keys']})
-        self.assertEqual([], list(keydb.getAuthorizedKeys('bob')))
+        keydb = checkers.InMemorySSHKeyDB({b'alice': [b'keys']})
+        self.assertEqual([], list(keydb.getAuthorizedKeys(b'bob')))
 
 
     def test_allKeysForAuthorizedUser(self):
@@ -706,8 +689,8 @@
         L{checkers.InMemorySSHKeyDB}, an iterator with all the keys
         is returned by L{checkers.InMemorySSHKeyDB.getAuthorizedKeys}
         """
-        keydb = checkers.InMemorySSHKeyDB({'alice': ['a', 'b']})
-        self.assertEqual(['a', 'b'], list(keydb.getAuthorizedKeys('alice')))
+        keydb = checkers.InMemorySSHKeyDB({b'alice': [b'a', b'b']})
+        self.assertEqual([b'a', b'b'], list(keydb.getAuthorizedKeys(b'alice')))
 
 
 
@@ -724,15 +707,15 @@
         mockos.path.makedirs()
 
         self.userdb = UserDatabase()
-        self.userdb.addUser('alice', 'password', 1, 2, 'alice lastname',
-                            mockos.path.path, '/bin/shell')
+        self.userdb.addUser(b'alice', b'password', 1, 2, b'alice lastname',
+                            mockos.path.path, b'/bin/shell')
 
         self.sshDir = mockos.path.child('.ssh')
         self.sshDir.makedirs()
         authorizedKeys = self.sshDir.child('authorized_keys')
-        authorizedKeys.setContent('key 1\nkey 2')
+        authorizedKeys.setContent(b'key 1\nkey 2')
 
-        self.expectedKeys = ['key 1', 'key 2']
+        self.expectedKeys = [b'key 1', b'key 2']
 
 
     def test_implementsInterface(self):
@@ -762,11 +745,11 @@
         C{~/.ssh/authorized_keys} and C{~/.ssh/authorized_keys2} is returned
         by L{checkers.UNIXAuthorizedKeysFiles.getAuthorizedKeys}.
         """
-        self.sshDir.child('authorized_keys2').setContent('key 3')
+        self.sshDir.child('authorized_keys2').setContent(b'key 3')
         keydb = checkers.UNIXAuthorizedKeysFiles(self.userdb,
                                                  parseKey=lambda x: x)
-        self.assertEqual(self.expectedKeys + ['key 3'],
-                         list(keydb.getAuthorizedKeys('alice')))
+        self.assertEqual(self.expectedKeys + [b'key 3'],
+                         list(keydb.getAuthorizedKeys(b'alice')))
 
 
     def test_ignoresNonexistantFile(self):
@@ -778,7 +761,7 @@
         keydb = checkers.UNIXAuthorizedKeysFiles(self.userdb,
                                                  parseKey=lambda x: x)
         self.assertEqual(self.expectedKeys,
-                         list(keydb.getAuthorizedKeys('alice')))
+                         list(keydb.getAuthorizedKeys(b'alice')))
 
 
     def test_ignoresUnreadableFile(self):
@@ -791,7 +774,7 @@
         keydb = checkers.UNIXAuthorizedKeysFiles(self.userdb,
                                                  parseKey=lambda x: x)
         self.assertEqual(self.expectedKeys,
-                         list(keydb.getAuthorizedKeys('alice')))
+                         list(keydb.getAuthorizedKeys(b'alice')))
 
 
 
@@ -816,8 +799,8 @@
 
     def setUp(self):
         self.credentials = SSHPrivateKey(
-            'alice', 'ssh-rsa', keydata.publicRSA_openssh, 'foo',
-             keys.Key.fromString(keydata.privateRSA_openssh).sign('foo'))
+             b'alice', b'ssh-rsa', keydata.publicRSA_openssh, b'foo',
+             keys.Key.fromString(keydata.privateRSA_openssh).sign(b'foo'))
         self.keydb = _KeyDB(lambda _: [
             keys.Key.fromString(keydata.publicRSA_openssh)])
         self.checker = checkers.SSHPublicKeyChecker(self.keydb)
@@ -838,7 +821,7 @@
         Calling L{checkers.SSHPublicKeyChecker.requestAvatarId} with
         credentials that have a bad key fails with L{keys.BadKeyError}.
         """
-        self.credentials.blob = ''
+        self.credentials.blob = b''
         self.failureResultOf(self.checker.requestAvatarId(self.credentials),
                              keys.BadKeyError)
 
@@ -862,7 +845,7 @@
         L{UnauthorizedLogin}.
         """
         self.credentials.signature = (
-            keys.Key.fromString(keydata.privateDSA_openssh).sign('foo'))
+            keys.Key.fromString(keydata.privateDSA_openssh).sign(b'foo'))
         self.failureResultOf(self.checker.requestAvatarId(self.credentials),
                              UnauthorizedLogin)
 
@@ -889,4 +872,4 @@
         callbacks with the username.
         """
         d = self.checker.requestAvatarId(self.credentials)
-        self.assertEqual('alice', self.successResultOf(d))
+        self.assertEqual(b'alice', self.successResultOf(d))

Modified: trunk/twisted/python/compat.py
==============================================================================
--- trunk/twisted/python/compat.py	(original)
+++ trunk/twisted/python/compat.py	Thu Mar  3 23:41:01 2016
@@ -612,6 +612,18 @@
 @rtype: L{list}
 """
 
+def _keys(d):
+    """
+    Return a list of the keys of C{d}.
+
+    @type d: L{dict}
+    @rtype: L{list}
+    """
+    if _PY3:
+        return list(d.keys())
+    else:
+        return d.keys()
+
 
 
 def bytesEnviron():
@@ -665,6 +677,12 @@
     "twisted.python.compat",
     "OrderedDict")
 
+if _PY3:
+    from base64 import encodebytes as _b64encodebytes
+    from base64 import decodebytes as _b64decodebytes
+else:
+    from base64 import encodestring as _b64encodebytes
+    from base64 import decodestring as _b64decodebytes
 
 
 __all__ = [
@@ -696,4 +714,7 @@
     "urlquote",
     "urlunquote",
     "cookielib",
+    "_keys",
+    "_b64encodebytes",
+    "_b64decodebytes",
 ]

Modified: trunk/twisted/python/dist3.py
==============================================================================
--- trunk/twisted/python/dist3.py	(original)
+++ trunk/twisted/python/dist3.py	Thu Mar  3 23:41:01 2016
@@ -47,6 +47,8 @@
     "twisted.application.strports",
     "twisted.application.test",
     "twisted.conch",
+    "twisted.conch.checkers",
+    "twisted.conch.error",
     "twisted.conch.ssh",
     "twisted.conch.ssh._cryptography_backports",
     "twisted.conch.ssh.common",
@@ -161,6 +163,7 @@
     "twisted.python.deprecate",
     "twisted.python.dist3",
     "twisted.python.failure",
+    "twisted.python.fakepwd",
     "twisted.python.filepath",
     "twisted.python.lockfile",
     "twisted.python.log",
@@ -249,6 +252,7 @@
     "twisted.application.test.test_internet",
     "twisted.application.test.test_service",
     "twisted.conch.test.test_keys",
+    "twisted.conch.test.test_checkers",
     "twisted.cred.test.test_cramauth",
     "twisted.cred.test.test_cred",
     "twisted.cred.test.test_digestauth",
@@ -466,9 +470,6 @@
     "twisted.names.root",
     # Echo is ported for twisted.application tests:
     "twisted.protocols.wire",
-    # Required by twisted.test.test_twistd
-    # https://twistedmatrix.com/trac/ticket/7958
-    "twisted.python.fakepwd",
     # Missing test coverage:
     "twisted.protocols.loopback",
     # Minimally used by setup3.py:
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.