Esempio n. 1
0
	def testConversationIds(self):
		'''Test the increment of conversation ids'''
		db = supersimpledb.MurmeliDb()
		DbI.setDb(db)
		# If we start with an empty db, the id should start at 1
		for i in range(100):
			self.assertEqual(DbI.getNewConversationId(), i+1, "id should be 1 more than i")
		# Add profiles for us and a messages sender
		DbI.updateProfile("F055", {"keyid":"ZYXW987", "status":"self", "name":"That's me"})
		DbI.updateProfile("ABC312", {"keyid":"ZYXW987", "status":"trusted", "name":"Best friend"})
		# Add an inbox message with a conversation id
		DbI.addToInbox({"messageBody":"Gorgonzola and Camembert", "timestamp":"early 2017",
			"fromId":"ABC312"})
		# Get this message again, and check the conversation id, should be 101
		msg0 = DbI.getInboxMessages()[0]
		self.assertEqual(msg0.get("conversationid", 0), 101, "Id should now be 101")

		# Add another message with the parent hash referring to the first one
		DbI.addToInbox({"messageBody":"Fried egg sandwich", "timestamp":"middle 2017",
			"fromId":"ABC312", "parentHash":'40e98bae7a811c23b59b89bd0f11b0a0'})
		msg1 = DbI.getInboxMessages()[0]
		self.assertTrue("Fried egg" in msg1.get("messageBody", ""), "Fried egg should be first")
		self.assertEqual(msg1.get("conversationid", 0), 101, "Id should now also be 101")

		# Add another message with an unrecognised parent hash
		DbI.addToInbox({"messageBody":"Red wine and chocolate", "timestamp":"late 2017",
			"fromId":"ABC312", "parentHash":'ff3'})
		msg2 = DbI.getInboxMessages()[0]
		self.assertTrue("Red wine" in msg2.get("messageBody", ""), "Red wine should be first")
		self.assertEqual(msg2.get("conversationid", 0), 102, "Id should take 102")

		# done
		DbI.releaseDb()
Esempio n. 2
0
	def testOutbox(self):
		'''Test the storage and retrieval of messages in the outbox'''
		db = supersimpledb.MurmeliDb()
		DbI.setDb(db)
		self.assertEqual(len(DbI.getOutboxMessages()), 0, "Outbox should be empty")
		# Add profile for this the target recipient
		DbI.updateProfile("ABC312", {"keyid":"ZYXW987", "status":"trusted", "name":"Best friend"})

		# add one message to the outbox
		DbI.addToOutbox(ExampleMessage(["ABC312"], "Doesn't matter really what the message is"))
		self.assertEqual(len(DbI.getOutboxMessages()), 1, "Outbox should have one message")
		self.checkMessageIndexes(DbI.getOutboxMessages())
		DbI.addToOutbox(ExampleMessage(["ABC312"], "A second message"))
		self.assertEqual(len(DbI.getOutboxMessages()), 2, "Outbox should have 2 messages")
		self.checkMessageIndexes(DbI.getOutboxMessages())
		self.assertTrue(DbI.deleteFromOutbox(0))
		self.assertEqual(len(DbI.getOutboxMessages()), 1, "Outbox should only have 1 message (1 empty)")
		nonEmptyMessages = DbI.getOutboxMessages()
		self.assertEqual(len(nonEmptyMessages), 1, "Outbox should only have 1 non-empty message")
		self.assertEqual(nonEmptyMessages[0]["_id"], 1, "Message 0 should have index 1")
		# See if index of third message is properly assigned
		DbI.addToOutbox(ExampleMessage(["ABC312"], "A third message"))
		self.assertEqual(len(DbI.getOutboxMessages()), 2, "Outbox should have 2 messages again")
		self.assertEqual(DbI.getOutboxMessages()[0]["_id"], 1, "Message 0 should have index 1")
		self.assertEqual(DbI.getOutboxMessages()[1]["_id"], 2, "Message 1 should have index 2")

		# done
		DbI.releaseDb()
Esempio n. 3
0
 def processPendingContacts(torId):
     print("Process pending contact accept responses from:", torId)
     foundReq = False
     for resp in DbI.getPendingContactMessages(torId):
         name = resp.get("fromName", None)
         if not name:
             profile = DbI.getProfile(torId)
             name = profile["displayName"]
         print("Found pending contact accept request from: ", name)
         # Check signature using keyring
         _, signatureKey = CryptoClient.decryptAndCheckSignature(
             resp.get("encryptedMsg", None))
         if signatureKey:
             foundReq = True
             # Insert new message into inbox with message contents
             rowToStore = {
                 "messageType": "contactresponse",
                 "fromId": resp.get("fromId", None),
                 "fromName": name,
                 "accepted": True,
                 "messageBody": resp.get("messageBody", ""),
                 "timestamp": resp.get("timestamp", None),
                 "messageRead": True,
                 "messageReplied": True,
                 "recipients": DbI.getOwnTorid()
             }
             DbI.addToInbox(rowToStore)
     if foundReq:
         DbI.updateProfile(torId, {"status": "untrusted"})
         # Delete all pending contact responses from this torId
         DbI.deletePendingContactMessages(torId)
Esempio n. 4
0
    def handleInitiate(torId, displayName):
        '''We have requested contact with another id, so we can set up
		this new contact's name with a status of "requested"'''
        # TODO: If row already exists then get status (and name/displayname) and error with it
        # Add new row in db with id, name and "requested"
        if torId and torId != DbI.getOwnTorid():
            DbI.updateProfile(
                torId, {
                    'displayName': displayName,
                    'name': displayName,
                    'status': 'requested'
                })
Esempio n. 5
0
    def handleReceiveAccept(torId, name, keyStr):
        '''We have requested contact with another id, and this has now been accepted.
		So we can import their public key into our keyring and update their status
		from "requested" to "untrusted"'''
        # Use keyStr to update keyring and get the keyId
        keyId = CryptoClient.importPublicKey(keyStr)
        # Store the keyId and name in their existing row, and update status to "untrusted"
        DbI.updateProfile(torId, {
            "name": name,
            "status": "untrusted",
            "keyid": keyId
        })
Esempio n. 6
0
 def keyFingerprintChecked(torId):
     '''The fingerprint of this contact's public key has been checked (over a separate channel)'''
     # Check that userid exists and that status is currently "untrusted" (trusted also doesn't hurt)
     profile = DbI.getProfile(torId)
     if profile and profile["status"] in ["untrusted", "trusted"]:
         # Update the user's status to trusted
         DbI.updateProfile(torId, {"status": "trusted"})
         # Trigger a StatusNotify to tell them we're online
         notify = StatusNotifyMessage(online=True,
                                      ping=True,
                                      profileHash=None)
         notify.recipients = [torId]
         DbI.addToOutbox(notify)
Esempio n. 7
0
 def setUp(self):
     Config.load()
     CryptoClient.useTestKeyring()
     self.FRIEND_TORID = "zo7quhgn1nq1uppt"
     FRIEND_KEYID = "3B898548F994C536"
     TestUtils.setupOwnProfile("46944E14D24D711B")  # id of key1
     DbI.updateProfile(
         self.FRIEND_TORID, {
             "status": "trusted",
             "keyid": FRIEND_KEYID,
             "name": "Norbert Jones",
             "displayName": "Uncle Norbert"
         })
     TestUtils.setupKeyring(["key1_private", "key2_public"])
Esempio n. 8
0
	def testBasics(self):
		'''Testing the basics of the interface with a super-simple db'''

		# Create new, empty database without file-storage
		db = supersimpledb.MurmeliDb()
		DbI.setDb(db)

		# Lists should be empty
		self.assertEqual(len(DbI.getMessageableProfiles()), 0, "Should be 0 messageables")
		self.assertEqual(len(DbI.getTrustedProfiles()), 0, "Should be 0 trusted")
		self.assertFalse(DbI.hasFriends(), "Shouldn't have any friends")

		# Store some profiles
		DbI.updateProfile("abc123", {"keyid":"ZYXW987", "status":"self", "name":"That's me"})
		DbI.updateProfile("def123", {"keyid":"JKLM987", "status":"trusted", "name":"Jimmy"})
		DbI.updateProfile("ghi123", {"keyid":"TUVWX987", "status":"untrusted", "name":"Dave"})

		# Get my ids
		self.assertEqual(DbI.getOwnTorid(), "abc123", "Should find correct tor id")
		self.assertEqual(DbI.getOwnKeyid(), "ZYXW987", "Should find correct key id")

		# Get all profiles
		self.assertEqual(len(DbI.getProfiles()), 3, "Should be three profiles in total")
		self.assertEqual(len(DbI.getMessageableProfiles()), 2, "Should be two messageables")
		self.assertEqual(len(DbI.getTrustedProfiles()), 1, "Should be one trusted")
		self.assertEqual(DbI.getTrustedProfiles()[0]['displayName'], "Jimmy", "Jimmy should be trusted")
		self.assertTrue(DbI.hasFriends(), "Should have friends")

		# Update an existing profile
		DbI.updateProfile("def123", {"displayName":"Slippin' Jimmy"})
		self.assertEqual(len(DbI.getTrustedProfiles()), 1, "Should still be one trusted")
		self.assertEqual(DbI.getTrustedProfiles()[0]['displayName'], "Slippin' Jimmy", "Slippin' Jimmy should be trusted")

		# Finished
		DbI.releaseDb()
Esempio n. 9
0
 def setupOwnProfile(keyId):
     tempDb = MurmeliDb()
     DbI.setDb(tempDb)
     DbI.updateProfile(
         TestUtils._ownTorId, {
             "status":
             "self",
             "ownprofile":
             True,
             "keyid":
             keyId,
             "name":
             "Geoffrey Lancaster",
             "displayName":
             "Me",
             "description":
             "Ä fictitious person with a couple of Umläute in his description."
         })
Esempio n. 10
0
	def testAvatars(self):
		'''Test the loading, storing and exporting of binary avatar images'''
		db = supersimpledb.MurmeliDb()
		DbI.setDb(db)
		inputPath = "testdata/example-avatar.jpg"
		outPath = "cache/avatar-deadbeef.jpg"
		if os.path.exists(outPath):
			os.remove(outPath)
		os.makedirs('cache', exist_ok=True)
		self.assertFalse(os.path.exists(outPath))
		DbI.updateProfile("deadbeef", {"profilepicpath":inputPath})
		# Output file still shouldn't exist, we didn't give a path to write picture to
		self.assertFalse(os.path.exists(outPath))
		DbI.updateProfile("deadbeef", {"profilepicpath":inputPath}, "cache")
		# Output file should exist now
		self.assertTrue(os.path.exists(outPath))
		# TODO: Any way to compare input with output?  They're not the same.
		DbI.releaseDb()
Esempio n. 11
0
 def checkAllContactsKeys():
     '''Return a list of names for which the key can't be found'''
     nameList = []
     for c in DbI.getMessageableProfiles():
         torId = c['torid'] if c else None
         if torId:
             keyId = c['keyid']
             if not keyId:
                 print("No keyid found for torid", torId)
                 nameList.append(c['displayName'])
             elif not CryptoClient.getPublicKey(keyId):
                 print("CryptoClient hasn't got a public key for torid",
                       torId)
                 nameList.append(c['displayName'])
             if not keyId or not CryptoClient.getPublicKey(keyId):
                 # We haven't got their key in our keyring!
                 DbI.updateProfile(torId, {"status": "requested"})
     return nameList
Esempio n. 12
0
 def finish(self):
     '''Finished the key gen'''
     # Store key, name in the database for our own profile
     selectedKey = self.privateKeys[self.keypairListWidget.currentRow()]
     ownid = TorClient.getOwnId()
     # See if a name was entered before, if so use that
     myname = self.keygenParamBoxes['name'].text()
     if not myname:
         # Extract the name from the string which comes back from the key as "'Some Name (no comment) <*****@*****.**>'"
         myname = self.extractName(selectedKey['uids'])
     profile = {
         "name": myname,
         "keyid": selectedKey['keyid'],
         "torid": ownid,
         "status": "self",
         "ownprofile": True
     }
     # Store this in the database
     DbI.updateProfile(ownid, profile)
     return True
Esempio n. 13
0
    def handleAccept(torId):
        '''We want to accept a contact request, so we need to find the request(s),
		and use it/them to update our keyring and our database entry'''

        # Get this person's current status from the db, if available
        profile = DbI.getProfile(torId)
        status = profile.get("status", None) if profile else None

        # Look for the contact request(s) in the inbox, and extract the name and publicKey
        senderName, senderKeystr, directRequest = ContactMaker.getContactRequestDetails(
            torId)
        keyValid = senderKeystr and len(senderKeystr) > 20

        if keyValid:
            if status in [None, "requested"]:
                # add key to keyring
                keyId = CryptoClient.importPublicKey(senderKeystr)
                # work out what name and status to stores
                storedSenderName = profile["name"] if profile else None
                nameToStore = storedSenderName if storedSenderName else senderName
                statusToStore = "untrusted" if directRequest else "pending"
                # add or update the profile
                DbI.updateProfile(
                    torId, {
                        "status": statusToStore,
                        "keyid": keyId,
                        "name": nameToStore,
                        "displayName": nameToStore
                    })
                ContactMaker.processPendingContacts(torId)
            elif status == "pending":
                print("Request already pending, nothing to do")
            elif status in ["untrusted", "trusted"]:
                # set status to untrusted?  Send response?
                print("Trying to handle an accept but status is already",
                      status)
            # Move all corresponding requests to be regular messages instead
            DbI.changeRequestMessagesToRegular(torId)
        else:
            print("Trying to handle an accept but key isn't valid")
Esempio n. 14
0
 def handleDeleteContact(torId):
     '''For whatever reason, we don't trust this contact any more, so status is set to "deleted"'''
     if torId and torId != DbI.getOwnTorid():
         DbI.updateProfile(torId, {"status": "deleted"})
Esempio n. 15
0
    def handleReceiveDeny(torId):
        '''We have requested contact with another id, but this has been denied.
		So we need to update their status accordingly'''
        if torId and torId != DbI.getOwnTorid():
            DbI.updateProfile(torId, {"status": "deleted"})
Esempio n. 16
0
 def dealWithAsymmetricMessage(message):
     '''Decide what to do with the given asymmetric message'''
     if message.senderId == DbI.getOwnTorid():
         print("*** Shouldn't receive a message from myself!")
         return
     # Sort message according to type
     if message.messageType == Message.TYPE_CONTACT_RESPONSE:
         print("Received a contact accept from", message.senderId, "name",
               message.senderName)
         if MessageShuffler._isProfileStatusOk(
                 message.senderId, ['pending', 'requested', 'untrusted']):
             print(message.senderName, "'s public key is",
                   message.senderKey)
             ContactMaker.handleReceiveAccept(message.senderId,
                                              message.senderName,
                                              message.senderKey)
             # Store new message in inbox
             rowToStore = {
                 "messageType": "contactresponse",
                 "fromId": message.senderId,
                 "fromName": message.senderName,
                 "messageBody": message.introMessage,
                 "accepted": True,
                 "messageRead": False,
                 "messageReplied": False,
                 "timestamp": message.timestamp,
                 "recipients": DbI.getOwnTorid()
             }
             DbI.addToInbox(rowToStore)
         elif MessageShuffler._isProfileStatusOk(message.senderId,
                                                 [None, 'blocked']):
             print(
                 "Received a contact response but I didn't send them a request!"
             )
             print("Encrypted contents are:", message.encryptedContents)
             rowToStore = {
                 "messageType": "contactresponse",
                 "fromId": message.senderId,
                 "fromName": message.senderName,
                 "messageBody": message.introMessage,
                 "accepted": True,
                 "timestamp": message.timestamp,
                 "encryptedMsg": message.encryptedContents
             }
             DbI.addMessageToPendingContacts(rowToStore)
     elif message.messageType == Message.TYPE_STATUS_NOTIFY:
         if message.online:
             print("One of our contacts has just come online- ",
                   message.senderId, "and hash is", message.profileHash)
             prof = DbI.getProfile(message.senderId)
             if prof:
                 storedHash = prof.get("profileHash", "empty")
                 if message.profileHash != storedHash:
                     reply = InfoRequestMessage(
                         infoType=InfoRequestMessage.INFO_PROFILE)
                     reply.recipients = [message.senderId]
                     DbI.addToOutbox(reply)
                 if message.ping:
                     print("Now sending back a pong, too")
                     reply = StatusNotifyMessage(online=True,
                                                 ping=False,
                                                 profileHash=None)
                     reply.recipients = [message.senderId]
                     DbI.addToOutbox(reply)
                 else:
                     print("It's already a pong so I won't reply")
             Contacts.instance().comeOnline(message.senderId)
         else:
             print("One of our contacts is going offline -",
                   message.senderId)
             Contacts.instance().goneOffline(message.senderId)
     elif message.messageType == Message.TYPE_INFO_REQUEST:
         print("I've received an info request message for type",
               message.infoType)
         if MessageShuffler._isProfileStatusOk(message.senderId,
                                               ['trusted']):
             reply = InfoResponseMessage(message.messageType)
             reply.recipients = [message.senderId]
             DbI.addToOutbox(reply)
     elif message.messageType == Message.TYPE_INFO_RESPONSE:
         if message.profile and MessageShuffler._isProfileStatusOk(
                 message.senderId, ['trusted', 'untrusted']):
             if message.profileHash:
                 message.profile['profileHash'] = message.profileHash
             DbI.updateProfile(message.senderId, message.profile,
                               Config.getWebCacheDir())
     elif message.messageType == Message.TYPE_FRIEND_REFERRAL:
         print("I've received a friend referral message from:",
               message.senderId, "for:", message.friendName)
         if MessageShuffler._isProfileStatusOk(message.senderId,
                                               ['trusted']):
             # Store new referral message in inbox
             rowToStore = {
                 "messageType": "contactrefer",
                 "fromId": message.senderId,
                 "friendId": message.friendId,
                 "friendName": message.friendName,
                 "messageBody": message.message,
                 "publicKey": message.publicKey,
                 "timestamp": message.timestamp,
                 "messageRead": False,
                 "messageReplied": False
             }
             DbI.addToInbox(rowToStore)
     elif message.messageType == Message.TYPE_FRIENDREFER_REQUEST:
         print("I've received a friend referral request from:",
               message.senderId, "who wants me to refer:", message.friendId)
         if MessageShuffler._isProfileStatusOk(message.senderId,
                                               ['trusted']):
             # Store message in the inbox
             rowToStore = {
                 "messageType": "referrequest",
                 "fromId": message.senderId,
                 "friendId": message.friendId,
                 "friendName": message.friendName,
                 "messageBody": message.message,
                 "publicKey": message.publicKey,
                 "timestamp": message.timestamp,
                 "messageRead": False,
                 "messageReplied": False
             }
             DbI.addToInbox(rowToStore)
     elif message.messageType == Message.TYPE_ASYM_MESSAGE:
         print(
             "It's a general kind of message, this should go in the Inbox, right?"
         )
         if MessageShuffler._isProfileStatusOk(message.senderId,
                                               ['trusted', 'untrusted']):
             rowToStore = {
                 "messageType": "normal",
                 "fromId": message.senderId,
                 "messageBody": message.messageBody,
                 "timestamp": message.timestamp,
                 "messageRead": False,
                 "messageReplied": False,
                 "recipients": message.sendTo,
                 "parentHash": message.replyToHash
             }
             DbI.addToInbox(rowToStore)
             Contacts.instance().comeOnline(message.senderId)
     else:
         # It's another asymmetric message type
         print("Hä?  What kind of asymmetric message type is that? ",
               message.messageType)
Esempio n. 17
0
 def handleDeny(torId):
     '''We want to deny a contact request - remember that this id is blocked'''
     DbI.updateProfile(torId, {"status": "blocked"})
     # Delete request from Inbox
     message = ContactMaker._getInboxMessage(torId, "contactrequest")
     DbI.deleteFromInbox(message.get("_id"))
Esempio n. 18
0
    def servePage(self, view, url, params):
        self.requirePageResources([
            'button-addperson.png', 'button-drawgraph.png', 'avatar-none.jpg'
        ])
        DbI.exportAllAvatars(Config.getWebCacheDir())
        if url == "/add" or url == "/add/":
            contents = self.generateAddPage()
            view.setHtml(contents)
            return
        elif url == "/submitaddrequest":
            print("Submit add request!:", url)
            if len(params) > 0:
                # request to add a new friend
                recipientid = params.get('murmeliid', '')
                dispname = params.get('displayname', '')
                intromessage = params.get('intromessage', '')
                if len(recipientid) == 16:
                    # TODO: How to react if: person already added (untrusted/trusted); request already sent (requested)
                    # update the database accordingly
                    ContactMaker.handleInitiate(recipientid, dispname)
                    print("I should send an add request to '%s' now." %
                          recipientid)
                    outmsg = message.ContactRequestMessage(
                        introMessage=intromessage)
                    outmsg.recipients = [recipientid]
                    DbI.addToOutbox(outmsg)
                else:
                    print("Hmm, show an error message here?")
                # in any case, go back to contact list
                url = "/" + recipientid
                # ensure that picture is generated for new id
                DbI.exportAllAvatars(Config.getWebCacheDir())
        contents = None
        userid = None
        pageParams = {}
        # Split url into components /userid/command
        command = [i for i in url.split("/") if i != ""]
        if len(command) > 0 and len(command[0]) == 16 and re.match(
                "([a-zA-Z0-9]+)$", command[0]):
            userid = command[0]
            # check for command edit or submit-edit
            if len(command) == 2:
                if command[1] == "edit":
                    contents = self.generateListPage(
                        doEdit=True, userid=userid)  # show edit fields
                elif command[1] == "submitedit":
                    DbI.updateProfile(userid, params, Config.getWebCacheDir())
                    # TODO: If we've updated our own details, can we trigger a broadcast?
                    # don't generate contents, go back to details
                elif command[1] == "delete":
                    ContactMaker.handleDeleteContact(userid)
                    userid = None
                elif command[1] == "checkfingerprint":
                    contents = self.generateFingerprintsPage(userid)
                elif command[1] == "checkedfingerprint":
                    givenAnswer = int(params.get('answer', -1))
                    fc = self._makeFingerprintChecker(userid)
                    expectedAnswer = fc.getCorrectAnswer()
                    if expectedAnswer == givenAnswer:
                        ContactMaker.keyFingerprintChecked(userid)
                        # Show page again
                        contents = self.generateFingerprintsPage(userid)
                    else:
                        # Add a message to show when the list page is re-generated
                        pageParams['fingerprint_check_failed'] = True
            elif len(command) == 3 and command[1] == "refer" and len(
                    command[2]) == 16:
                intro = str(params.get('introMessage', ""))
                ContactMaker.sendReferralMessages(command[0], command[2],
                                                  intro)
                pageParams['message_sent'] = True
                # go back to details page
            elif len(command) == 3 and command[1] == "requestrefer" and len(
                    command[2]) == 16:
                intro = str(params.get('introMessage', ""))
                ContactMaker.sendReferRequestMessage(command[0], command[2],
                                                     intro)
                pageParams['message_sent'] = True
                # go back to details page

        # If we haven't got any contents yet, then do a show details
        if not contents:
            # Show details for selected userid (or for self if userid is None)
            contents = self.generateListPage(doEdit=False,
                                             userid=userid,
                                             extraParams=pageParams)

        view.setHtml(contents)