Esempio n. 1
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. 2
0
 def _createUnencryptedPayload(self):
     if self.senderId is None:
         self.senderId = DbI.getOwnTorid()
     return self.packBytesTogether([
         self.encodeNumberToBytes(self.messageType, 1), self.senderId,
         self._createSubpayload()
     ])
Esempio n. 3
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. 4
0
 def sendReferRequestMessage(sendToId, requestedId, intro):
     '''Send a message to sendToId, to ask that they recommend you to requestedId'''
     sendToProfile = DbI.getProfile(sendToId)
     if sendToProfile and sendToProfile.get("status", "nostatus") == "trusted" \
       and requestedId != DbI.getOwnTorid():
         print("Send message to", sendToId, "requesting referral of",
               requestedId)
         notify = ContactReferRequestMessage(friendId=requestedId,
                                             introMessage=intro)
         notify.recipients = [sendToId]
         DbI.addToOutbox(notify)
Esempio n. 5
0
 def generateAddPage(self):
     '''Build the form page for adding a new user, using the template'''
     bodytext = self.addtemplate.getHtml({"owntorid": DbI.getOwnTorid()})
     return self.buildPage({
         'pageTitle':
         I18nManager.getText("contacts.title"),
         'pageBody':
         bodytext,
         'pageFooter':
         "<p>Footer</p>"
     })
Esempio n. 6
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. 7
0
    def getSharedAndPossibleContacts(torid):
        '''Check which contacts we share with the given torid and which ones we could recommend to each other'''
        nameMap = {}
        ourContactIds = set()
        trustedContactIds = set()
        theirContactIds = set()
        # Get our id so we can exclude it from the sets
        myTorId = DbI.getOwnTorid()
        if torid == myTorId:
            return (None, None, None, None)
        # Find the contacts of the specified person
        selectedProfile = DbI.getProfile(torid)
        selectedContacts = selectedProfile.get(
            'contactlist', None) if selectedProfile else None
        if selectedContacts:
            for s in selectedContacts.split(","):
                if s and len(s) >= 16:
                    foundid = s[0:16]
                    if foundid != myTorId:
                        foundName = s[16:]
                        theirContactIds.add(foundid)
                        nameMap[foundid] = foundName
        foundTheirContacts = len(theirContactIds) > 0
        # Now get information about our contacts
        for c in DbI.getMessageableProfiles():
            foundid = c['torid']
            ourContactIds.add(foundid)
            if c['status'] == 'trusted' and foundid != torid:
                trustedContactIds.add(foundid)
            nameMap[foundid] = c['displayName']
            # Should we check the contact information too?
            if not foundTheirContacts:
                foundContacts = c.get('contactlist', None)
                if foundContacts:
                    for s in foundContacts.split(","):
                        if s[0:16] == torid:
                            theirContactIds.add(foundid)
        # Now we have three sets of torids: our contacts, our trusted contacts, and their contacts.
        sharedContactIds = ourContactIds.intersection(
            theirContactIds)  # might be empty
        suggestionsForThem = trustedContactIds.difference(theirContactIds)
        possibleForMe = theirContactIds.difference(ourContactIds)
        # TODO: Maybe subtract requested contacts from the "possibleForMe" set?

        # Some or all of these sets may be empty, but we still return the map so we can look up names
        return (sharedContactIds, suggestionsForThem, possibleForMe, nameMap)
Esempio n. 8
0
 def _createSubpayload(self):
     '''Use the stored fields to pack the payload contents together'''
     if self.senderKey is None: self.senderKey = self.getOwnPublicKey()
     # Get own torid and name
     if not self.senderId: self.senderId = DbI.getOwnTorid()
     if not self.senderName:
         self.senderName = DbI.getProfile().get('name', self.senderId)
     if not self.introMessage: self.introMessage = ""
     nameAsBytes = self.senderName.encode('utf-8')
     messageAsBytes = self.introMessage.encode('utf-8')
     print("Packing contact request with senderId", self.senderId)
     return self.packBytesTogether([
         self.senderId,
         self.encodeNumberToBytes(len(nameAsBytes), 4), nameAsBytes,
         self.encodeNumberToBytes(len(messageAsBytes), 4), messageAsBytes,
         self.senderKey
     ])
Esempio n. 9
0
 def dealWithUnencryptedMessage(message):
     '''Decide what to do with the given unencrypted message'''
     if message.messageType == Message.TYPE_CONTACT_REQUEST:
         print("Received a contact request from", message.senderId)
         # Check config to see whether we accept these or not
         if Config.getProperty(Config.KEY_ALLOW_FRIEND_REQUESTS) \
           and MessageShuffler._isProfileStatusOk(message.senderId, [None, 'requested', 'untrusted', 'trusted']):
             # Store new message in inbox
             rowToStore = {
                 "messageType": "contactrequest",
                 "fromId": message.senderId,
                 "fromName": message.senderName,
                 "messageBody": message.message,
                 "publicKey": message.publicKey,
                 "timestamp": message.timestamp,
                 "messageRead": False,
                 "messageReplied": False
             }
             DbI.addToInbox(rowToStore)
     elif message.messageType == Message.TYPE_CONTACT_RESPONSE:
         print(
             "It's an unencrypted contact response, so it must be a refusal"
         )
         sender = DbI.getProfile(message.senderId)
         if MessageShuffler._isProfileStatusOk(message.senderId,
                                               ['requested']):
             senderName = sender.get("displayName") if sender else ""
             ContactMaker.handleReceiveDeny(message.senderId)
             # Store new message in inbox
             rowToStore = {
                 "messageType": "contactresponse",
                 "fromId": message.senderId,
                 "fromName": senderName,
                 "messageBody": "",
                 "accepted": False,
                 "messageRead": False,
                 "messageReplied": False,
                 "timestamp": message.timestamp,
                 "recipients": DbI.getOwnTorid()
             }
             DbI.addToInbox(rowToStore)
     else:
         print("Hä?  It's unencrypted but the message type is",
               message.messageType)
Esempio n. 10
0
    def dealWithMessage(message):
        '''Examine the received message and decide what to do with it'''
        print("Hmm, the MessageShuffler has been given some kind of message")
        # We must be online if we've received a message
        Contacts.instance().comeOnline(DbI.getOwnTorid())

        if message.senderMustBeTrusted:
            sender = DbI.getProfile(message.senderId)
            if not sender or sender['status'] != "trusted":
                return  # throw message away

        if not message.isComplete():
            print("A message of type", message.encryptionType,
                  "was received but it's not complete - throwing away")
            return  # throw message away

        # if it's not encrypted, it's for us -> save in inbox
        if message.encryptionType == Message.ENCTYPE_NONE:
            MessageShuffler.dealWithUnencryptedMessage(message)

        elif message.encryptionType == Message.ENCTYPE_SYMM:
            # if it's symmetric, forget it for now
            pass

        elif message.encryptionType == Message.ENCTYPE_ASYM:
            MessageShuffler.dealWithAsymmetricMessage(message)

        elif message.encryptionType == Message.ENCTYPE_RELAY:
            # Get received bytes of message, and add to Outbox, send to everybody EXCEPT the sender
            bytesToSend = message.createOutput(None)
            if bytesToSend:
                # add to outbox, but don't send it back to message.senderId
                DbI.addRelayMessageToOutbox(bytesToSend, message.senderId)
        else:
            print("Hä?  What kind of encryption type is that? ",
                  message.encryptionType)

        # Log receipt of message (do we want to know about relays at all?)
        if message.encryptionType in [
                Message.ENCTYPE_NONE, Message.ENCTYPE_ASYM
        ]:
            logMessage = "Message of type: %s received from %s" % (
                message.getMessageTypeKey(), message.senderId)
            MessageShuffler.getTannoy().shout(logMessage)
Esempio n. 11
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. 12
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. 13
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. 14
0
    def servePage(self, view, url, params):
        print("Compose: %s, params %s" % (url, repr(params)))
        if url == "/start":
            self.requirePageResources(['default.css', 'jquery-3.1.1.js'])
            DbI.exportAllAvatars(Config.getWebCacheDir())
            parentHash = params.get("reply", None)
            recpts = params.get("sendto", None)
            # Build list of contacts to whom we can send
            userboxes = []
            for p in DbI.getMessageableProfiles():
                box = Bean()
                box.dispName = p['displayName']
                box.torid = p['torid']
                userboxes.append(box)
            pageParams = {
                "contactlist": userboxes,
                "parenthash": parentHash if parentHash else "",
                "webcachedir": Config.getWebCacheDir(),
                "recipientids": recpts
            }
            contents = self.buildPage({
                'pageTitle':
                I18nManager.getText("composemessage.title"),
                'pageBody':
                self.composetemplate.getHtml(pageParams),
                'pageFooter':
                "<p>Footer</p>"
            })
            view.setHtml(contents)
            # If we've got no friends, then warn, can't send to anyone
            if not DbI.hasFriends():
                view.page().mainFrame().evaluateJavaScript(
                    "window.alert('No friends :(');")

        elif url == "/send":
            print("Submit new message with params:", params)
            msgBody = params[
                'messagebody']  # TODO: check body isn't empty, throw an exception?
            parentHash = params.get("parenthash", None)
            recpts = params['sendto']
            # Make a corresponding message object and pass it on
            msg = message.RegularMessage(sendTo=recpts,
                                         messageBody=msgBody,
                                         replyToHash=parentHash)
            msg.recipients = recpts.split(",")
            DbI.addToOutbox(msg)
            # Save a copy of the sent message
            sentMessage = {
                "messageType": "normal",
                "fromId": DbI.getOwnTorid(),
                "messageBody": msgBody,
                "timestamp": msg.timestamp,
                "messageRead": True,
                "messageReplied": False,
                "recipients": recpts,
                "parentHash": parentHash
            }
            DbI.addToInbox(sentMessage)
            # Close window after successful send
            contents = self.buildPage({
                'pageTitle':
                I18nManager.getText("messages.title"),
                'pageBody':
                self.closingtemplate.getHtml(),
                'pageFooter':
                "<p>Footer</p>"
            })
            view.setHtml(contents)
Esempio n. 15
0
    def servePage(self, view, url, params):
        print("Special function:", url)
        if url == "/selectprofilepic":
            # Get home directory for file dialog
            homedir = os.path.expanduser("~/")
            fname = QFileDialog.getOpenFileName(
                view,
                I18nManager.getText("gui.dialogtitle.openimage"), homedir,
                I18nManager.getText("gui.fileselection.filetypes.jpg"))
            if fname:
                view.page().mainFrame().evaluateJavaScript(
                    "updateProfilePic('" + fname + "');")
        elif url == "/friendstorm":
            if not DbI.hasFriends():
                view.page().mainFrame().evaluateJavaScript(
                    "window.alert('No friends :(');")
                return
            # Launch a storm
            self.bs = Brainstorm(I18nManager.getText("contacts.storm.title"))
            self.bs.show()
            storm = Storm()
            # Build up Nodes and Edges using our contact list and if possible our friends' contact lists
            myTorId = DbI.getOwnTorid()
            friends = {}
            friendsOfFriends = {}
            for c in DbI.getMessageableProfiles():
                # print("Contact: id:'%s' name:'%s'" % (c['torid'], c['displayName']))
                nodeid = storm.getUnusedNodeId()
                torid = c['torid']
                friends[torid] = nodeid
                storm.addNode(Node(None, nodeid, c['displayName']))
                friendsOfFriends[torid] = c.get('contactlist', "")
            # Also add ourselves
            c = DbI.getProfile()
            nodeid = storm.getUnusedNodeId()
            friends[c['torid']] = nodeid
            storm.addNode(Node(None, nodeid, c['displayName']))
            # Add edges
            for torid in friends:
                if torid != myTorId:
                    storm.addEdge(friends[torid], friends[myTorId])
            for torid in friendsOfFriends:
                if torid != myTorId:
                    ffList = friendsOfFriends[torid]
                    if ffList:
                        for ff in ffList.split(","):
                            if ff and len(ff) > 16:
                                ffTorid = ff[:16]
                                ffName = ff[16:]
                                if ffTorid != myTorId:
                                    if not friends.get(ffTorid, None):
                                        # Friend's friend is not in the list yet - add it
                                        nodeid = storm.getUnusedNodeId()
                                        friends[ffTorid] = nodeid
                                        storm.addNode(
                                            Node(None, nodeid, ffName))
                                    # Add edge from torid to ffTorid
                                    storm.addEdge(friends[torid],
                                                  friends[ffTorid])

            self.bs.setStorm(storm)
Esempio n. 16
0
    def generateListPage(self, doEdit=False, userid=None, extraParams=None):
        self.requirePageResources([
            'avatar-none.jpg', 'status-self.png', 'status-requested.png',
            'status-untrusted.png', 'status-trusted.png', 'status-pending.png'
        ])
        # List of contacts, and show details for the selected one (or self if userid=None)
        selectedprofile = DbI.getProfile(userid)
        if not selectedprofile:
            selectedprofile = DbI.getProfile()
        userid = selectedprofile['torid']
        ownPage = userid == DbI.getOwnTorid()

        # Build list of contacts
        userboxes = []
        currTime = datetime.datetime.now()
        for p in DbI.getProfiles():
            box = Bean()
            box.dispName = p['displayName']
            box.torid = p['torid']
            box.tilestyle = "contacttile" + ("selected"
                                             if p['torid'] == userid else "")
            box.status = p['status']
            isonline = Contacts.instance().isOnline(box.torid)
            lastSeen = Contacts.instance().lastSeen(box.torid)
            lastSeenTime = str(lastSeen.timetz())[:5] if lastSeen and (
                currTime - lastSeen).total_seconds() < 18000 else None
            if lastSeenTime:
                box.lastSeen = I18nManager.getText(
                    "contacts.onlinesince"
                    if isonline else "contacts.offlinesince") % lastSeenTime
            elif isonline:
                box.lastSeen = I18nManager.getText("contacts.online")
            else:
                box.lastSeen = None
            userboxes.append(box)

        # expand templates using current details
        lefttext = self.listtemplate.getHtml({
            'webcachedir':
            Config.getWebCacheDir(),
            'contacts':
            userboxes
        })
        pageProps = {
            "webcachedir": Config.getWebCacheDir(),
            'person': selectedprofile
        }
        # Add extra parameters if necessary
        if extraParams:
            pageProps.update(extraParams)

        # See which contacts we have in common with this person
        (sharedContactIds, possIdsForThem, possIdsForMe,
         nameMap) = ContactMaker.getSharedAndPossibleContacts(userid)
        sharedContacts = self._makeIdAndNameBeanList(sharedContactIds, nameMap)
        pageProps.update({"sharedcontacts": sharedContacts})
        possibleContacts = self._makeIdAndNameBeanList(possIdsForThem, nameMap)
        pageProps.update({"possiblecontactsforthem": possibleContacts})
        possibleContacts = self._makeIdAndNameBeanList(possIdsForMe, nameMap)
        pageProps.update({"possiblecontactsforme": possibleContacts})

        # Which template to use depends on whether we're just showing or also editing
        if doEdit:
            # Use two different details templates, one for self and one for others
            detailstemplate = self.editowndetailstemplate if ownPage else self.editdetailstemplate
            righttext = detailstemplate.getHtml(pageProps)
        else:
            detailstemplate = self.detailstemplate  # just show
            righttext = detailstemplate.getHtml(pageProps)

        contents = self.buildTwoColumnPage({
            'pageTitle':
            I18nManager.getText("contacts.title"),
            'leftColumn':
            lefttext,
            'rightColumn':
            righttext,
            'pageFooter':
            "<p>Footer</p>"
        })
        return contents