Ticket #4398: IUsernamePassword_over_pb.patch

File IUsernamePassword_over_pb.patch, 7.9 KB (added by Grindaizer, 10 years ago)

allow to defined custom hash metod to PB authentication.

  • test/test_pb.py

     
    11671167
    11681168
    11691169
     1170def DummyHash(salt, secret):
     1171    return salt + secret
     1172
     1173class HashMethodCredential(credentials.UsernamePassword):
     1174    implements(pb.IHashMethod)
     1175    def hashMethod(self, salt, secret):
     1176        return DummyHash(salt, secret)
     1177
     1178class DummyChallengerChecker(checkers.InMemoryUsernamePasswordDatabaseDontUse):
     1179    implements(pb.IChallenger)
     1180    def challengeFor(self, username):
     1181        return str(len(username))
     1182
     1183    def hashMethod(self, salt, secret):
     1184        return DummyHash(salt, secret)
     1185
     1186class DummyCrdentialWithBadHashMethod(credentials.UsernamePassword):
     1187    implements(pb.IHashMethod)
     1188    def hashMethod(self, salt, secret):
     1189        return "salt"
     1190
    11701191class NewCredLeakTests(unittest.TestCase):
    11711192    """
    11721193    Tests to try to trigger memory leaks.
     
    14051426        self.addCleanup(connector.disconnect)
    14061427        return d
    14071428
     1429    def test_loginLogoutWithIHashMethod(self):
     1430        """
     1431        Test that login can be performed with IUsernamePassword and IHashMethod
     1432        credentials and that when the connection is dropped the avatar is
     1433        logged out
     1434        """
    14081435
     1436        self.portal.registerChecker(
     1437            DummyChallengerChecker(user='pass'))
     1438        factory = pb.PBClientFactory()
     1439        creds = HashMethodCredential("user", "pass")
     1440
     1441        mind = "BRAINS!"
     1442
     1443        d = factory.login(creds, mind)
     1444        def cbLogin(perspective):
     1445            self.assertTrue(self.realm.lastPerspective.loggedIn)
     1446            self.assertIsInstance(perspective, pb.RemoteReference)
     1447            return self._disconnect(None, factory)
     1448        d.addCallback(cbLogin)
     1449
     1450        def cbLogout(ignored):
     1451            self.assertTrue(self.realm.lastPerspective.loggedOut)
     1452        d.addCallback(cbLogout)
     1453
     1454        connector = reactor.connectTCP("127.0.0.1", self.portno, factory)
     1455        self.addCleanup(connector.disconnect)
     1456        return d
     1457
     1458    def test_loginWithBadHashMethod(self):
     1459        """ Test that we cannot login if client and server do not use the
     1460        same hashMethod
     1461        """
     1462        self.portal.registerChecker(
     1463            DummyChallengerChecker(user='pass'))
     1464        factory = pb.PBClientFactory()
     1465        creds = DummyCrdentialWithBadHashMethod("user", "pass")
     1466
     1467        mind = "BRAINS!"
     1468        d = factory.login(creds, mind)
     1469        self.assertFailure(d, UnauthorizedLogin, u"tete")
     1470
     1471        d = gatherResults([d])
     1472
     1473        def cleanup(ignore):
     1474            errors = self.flushLoggedErrors(UnauthorizedLogin)
     1475            self.assertEqual(len(errors), 1)
     1476            return self._disconnect(None, factory)
     1477        d.addCallback(cleanup)
     1478
     1479        connector = reactor.connectTCP("127.0.0.1", self.portno, factory)
     1480        self.addCleanup(connector.disconnect)
     1481       
     1482        return d
     1483
    14091484    def test_logoutAfterDecref(self):
    14101485        """
    14111486        If a L{RemoteReference} to an L{IPerspective} avatar is decrefed and
  • spread/pb.py

     
    3737from twisted.python.hashlib import md5
    3838from twisted.internet import defer, protocol
    3939from twisted.cred.portal import Portal
    40 from twisted.cred.credentials import IAnonymous, ICredentials
     40from twisted.cred.credentials import IAnonymous, ICredentials, IUsernamePassword
    4141from twisted.cred.credentials import IUsernameHashedPassword, Anonymous
    4242from twisted.persisted import styles
    4343from twisted.python.components import registerAdapter
     
    10411041##         obj.__del__ = reallyDel
    10421042        del self.locallyCachedObjects[objectID]
    10431043
     1044# let users defined hash method and challenge (?salt).
     1045class IHashMethod(Interface):
     1046    def hashMethod(salt, data):
     1047        """ return hashed data using salf """
    10441048
     1049class IChallenger(IHashMethod):
     1050    def challengeFor(data):
     1051        """ get challenge (salt) for data """
    10451052
     1053
    10461054def respond(challenge, password):
    10471055    """Respond to a challenge.
    10481056
     
    11551163        if self._broker:
    11561164            self._broker.transport.loseConnection()
    11571165
    1158     def _cbSendUsername(self, root, username, password, client):
     1166    def _cbSendUsername(self, root, username, password, client, hashMethod=None):
    11591167        return root.callRemote("login", username).addCallback(
    1160             self._cbResponse, password, client)
     1168            self._cbResponse, password, client, hashMethod)
    11611169
    1162     def _cbResponse(self, (challenge, challenger), password, client):
    1163         return challenger.callRemote("respond", respond(challenge, password), client)
     1170    def _cbResponse(self, (challenge, challenger), password, client, hashMethod=None):
     1171        if not hashMethod:
     1172            hashMethod = respond
     1173        return challenger.callRemote("respond", hashMethod(challenge, password), client)
    11641174
    11651175
    11661176    def _cbLoginAnonymous(self, root, client):
     
    12001210
    12011211        if IAnonymous.providedBy(credentials):
    12021212            d.addCallback(self._cbLoginAnonymous, client)
     1213        elif IHashMethod.providedBy(credentials):
     1214            d.addCallback(
     1215                self._cbSendUsername, credentials.username,
     1216                credentials.password, client, credentials.hashMethod)
    12031217        else:
    12041218            d.addCallback(
    12051219                self._cbSendUsername, credentials.username,
     
    13601374        """
    13611375        Start of username/password login.
    13621376        """
    1363         c = challenge()
    1364         return c, _PortalAuthChallenger(self.portal, self.broker, username, c)
     1377        checker = self.portal.checkers.get(IUsernamePassword, None)
     1378        if checker and IChallenger.providedBy(checker):
     1379            c = checker.challengeFor(username)
     1380            hashMethod = checker.hashMethod
     1381        else:
     1382            c = challenge()
     1383            hashMethod = respond
     1384        return c, _PortalAuthChallenger(self.portal, self.broker, username, c,
     1385                                        hashMethod)
    13651386
    13661387
    13671388    def remote_loginAnonymous(self, mind):
     
    13851406    """
    13861407    Called with response to password challenge.
    13871408    """
    1388     implements(IUsernameHashedPassword, IUsernameMD5Password)
     1409    implements(IUsernameHashedPassword, IUsernamePassword)
    13891410
    1390     def __init__(self, portal, broker, username, challenge):
     1411    def __init__(self, portal, broker, username, challenge, hashMethod):
    13911412        self.portal = portal
    13921413        self.broker = broker
    13931414        self.username = username
    13941415        self.challenge = challenge
     1416        self.hashMethod = hashMethod
    13951417
    13961418
    13971419    def remote_respond(self, response, mind):
    13981420        self.response = response
     1421        # IUsernamePassword
     1422        self.password = response
    13991423        d = self.portal.login(self, mind, IPerspective)
    14001424        d.addCallback(self._cbLogin)
    14011425        return d
     
    14031427
    14041428    # IUsernameHashedPassword:
    14051429    def checkPassword(self, password):
    1406         return self.checkMD5Password(md5(password).digest())
     1430        return self.checkHashedPassword(self.hashMethod(self.challenge, password))
     1431        #return self.checkMD5Password(md5(password).digest())
    14071432
    14081433
    1409     # IUsernameMD5Password
    1410     def checkMD5Password(self, md5Password):
    1411         md = md5()
    1412         md.update(md5Password)
    1413         md.update(self.challenge)
    1414         correct = md.digest()
    1415         return self.response == correct
     1434    def checkHashedPassword(self, hashed):
     1435        return hashed == self.response
    14161436
    1417 
    14181437__all__ = [
    14191438    # Everything from flavors is exposed publically here.
    14201439    'IPBRoot', 'Serializable', 'Referenceable', 'NoSuchMethod', 'Root',
     
    14301449    'RemoteMethod', 'IPerspective', 'Avatar', 'AsReferenceable',
    14311450    'RemoteReference', 'CopyableFailure', 'CopiedFailure', 'failure2Copyable',
    14321451    'Broker', 'respond', 'challenge', 'PBClientFactory', 'PBServerFactory',
    1433     'IUsernameMD5Password',
     1452    'IUsernameMD5Password', 'IHashMethod', 'IChallenger',
    14341453    ]