Beispiel #1
0
def send_heartbeat(url, key):
    json_msg = {}
    json_msg['method'] = 'heartbeat'
    json_msg['id'] = 4
    params = {}
    params['kiosk-id'] = DIEID
    if UPTIME < 60:
        # under a minute
        uptimestr = "%.2f seconds" % UPTIME
    elif UPTIME < 60*60:
        # under a hour
        uptimestr = "%.2f minutes" % (UPTIME/(60.0))
    elif UPTIME < 60*60*24*3:
        # under 3 days
        uptimestr = "%.2f hours" % (UPTIME/(60.0*60.0))
    else:
        # over 3 days
        uptimestr = "%.2f days" % (UPTIME/(60.0*60.0*24.0))
        
    params['uptime'] = uptimestr
    params['sw-version'] = '1.0.0'
    params['time'] = datetime.utcnow()
    json_msg['params'] = params
    jsstr = json.dumps(json_msg)
    hash = MD5.new(jsstr).digest()
    baseconverter = BaseConverter('0123456789abcdef')
    sign_16encode = baseconverter.from_decimal(key.sign(hash, "")[0])
    #print "encoded: %s" % sign_16encode
    #print "signature: %d" % key.sign(hash, "")[0]
    #print "hash: %s"  % "".join(["%02x " % ord(x) for x in hash])
    headers = {'X-eko-signature': sign_16encode}
    print jsstr
    
    #test decoding
    x = key.publickey().verify(hash, (baseconverter.to_decimal(sign_16encode),))
    
    req = urllib2.Request(url, jsstr, headers)
    response = urllib2.urlopen(req)
    the_page = response.read()
    print the_page
Beispiel #2
0
def get_messages(url, key):
    json_msg = {}
    json_msg['method'] = 'get_messages'
    json_msg['id'] = 4
    json_msg['params'] = {'kiosk-id': DIEID}
    jsstr = json.dumps(json_msg)
    hash = MD5.new(jsstr).digest()
    baseconverter = BaseConverter('0123456789abcdef')
    sign_16encode = baseconverter.from_decimal(key.sign(hash, "")[0])
    #print "encoded: %s" % sign_16encode
    #print "signature: %d" % key.sign(hash, "")[0]
    #print "hash: %s"  % "".join(["%02x " % ord(x) for x in hash])
    headers = {'X-eko-signature': sign_16encode}
    print jsstr
    
    #test decoding
    x = key.publickey().verify(hash, (baseconverter.to_decimal(sign_16encode),))
    
    req = urllib2.Request(url, jsstr, headers)
    response = urllib2.urlopen(req)
    the_page = response.read()
    print the_page
Beispiel #3
0
class JSONRPCMethods(object):
    """
    .. py:class:: JSONRPCMethods(request)
    
    Contains methods accessible by the JSON RPC.
    
    :param request: The WebOb request object.
    """
    def __init__(self, request):
        self.request = request
        self.bconv = BaseConverter('0123456789abcdef')
    
    def _extract_signature_hdr(self):
        """
        .. py:func:: _extract_signature_hdr()
        
        Extracts the signature from the request header and calculates the request hash.
        
        :rtype (hash, (signature,)): A tuple containing the hash and signature.
        """
        hash = MD5.new(self.request.body).digest()
        
        try:
            signature = self.bconv.to_decimal(self.request.headers['X-eko-signature'])
        except KeyError:
            logging.critical("Client message did not provide verification signature")
        
        # useful for debugging RSA issues
        logging.debug("signature long : %d ::: " % signature)
        logging.debug("hdr: %s" % self.request.headers['X-eko-signature'])
        logging.debug("hash: %s"  % "".join(["%02x " % ord(x) for x in hash]))
        return (hash, (signature,))
    
    def _get_public_key(self, kiosk):
        """
        .. py:func:: _get_public_key(kiosk)
        
        Returns the public key for this kiosk, either from memcache or the datastore.
        
        :param kiosk: The kiosk object.
        :rtype RSAobj: A public key object.
        """
        # check memcache
        pubkey = memcache.get(kiosk.dieid, namespace='rsa-publickeys')
        if pubkey is not None:
            return pubkey
        else:
            # get encoded public key from database
            pubkey_n = self.bconv.to_decimal(kiosk.pubkey_n)
            pubkey_e = self.bconv.to_decimal(kiosk.pubkey_e)
            
            #create the public key
            try:
                pubkey = RSA.construct((pubkey_n, pubkey_e))
            except:
                logging.critical("Could not create public key for kiosk")
                return None
            if not memcache.add(kiosk.dieid, pubkey, namespace='rsa-publickeys'):
                logging.error("Unable to save rsa key to memcache.")
            return pubkey
    
    def get_messages(self, method, id, **kwargs):
        """
        .. py:func:: get_messages(method, id, **kwargs)
        
        Returns all server messages that are pending for the kiosk.
        
        :param method: The name of the requested json method (should be equal to 'get_messages')
        :param id: Unused
        :param **kwargs: Keyword arguments to the function. Should have non-unicode keys.
        :rtype dict: The response object. 
        Expects the following in kwargs:
        kiosk-id : The unique kiosk identifier
        
        Expects the following headers:
        X-eko-signature: RSA signed hash of request body.
        """
        
        dieid = kwargs['kiosk-id']
        
        logging.debug('device with id : %s is requesting incoming messages.' % dieid)
        
        # grab kiosk, internally utilises memcache
        kiosk = Kiosk.kiosk_from_dieid(dieid)
        
        if not kiosk:
            logging.warn('Unrecognised kiosk attempted to fetch server messages (IP: %s, dieid %s).' % (self.request.remote_addr, dieid))
            return self._standard_error_response(method, id, 'Not recognised', 20, 'Kiosk not registered with system.')
        
        logging.info('Receiving message fetch request from kiosk %s at IP %s' % (kiosk.name, self.request.remote_addr))
        
        hash, signature = self._extract_signature_hdr()
        
        if not signature[0]:
            # prepare error response
            return self._standard_error_response(method, id, 'Signature Absent', 10, 'Verification signature not present.')
        
        pubkey = self._get_public_key(kiosk)
        
        if not pubkey:
            return self._standard_error_response(method, id, 'Signature Fail', 11, 'Unable to create public key from config.')
        
        
        if pubkey and signature[0]:
            if pubkey.verify(hash, signature):
                #message is authentic
                messages = self._get_server_messages(kiosk)
                data = {}
                data['result'] = messages
                data['error'] = None
                data['id'] = id
                return data
            else:
                return self._standard_error_response(method, id, 'Signature Incorrect', 12, 'Verification signature failed check.')
    
    def _get_server_messages(self, kiosk):
        """
        .. py:func:: _get_server_messages(kiosk)
        
           Returns server messages that haven't yet been downloaded by a kiosk.
           
           :param kiosk: The kiosk object which owns the messages.
        """
        messages = kiosk.servermessage_set.filter('retrieved =', False).fetch(10)
        response = []
        for message in messages:
            dict = {}
            dict['msg'] = message.message
            dict['msg_type'] = message.msg_type
            dict['date'] = message.date
            message.retrieved = True
            message.retrieved_date = datetime.utcnow()
            message.put()
            response.append(dict)
        return response
    
    def _standard_error_response(self, func, id, ename, ecode, edesc):
        """Creates a json object in the format of an error"""
        resp = {}
        error_dict = {}
        error_dict['name'] = ename
        error_dict['code'] = ecode
        error_dict['message'] = edesc
        resp['method'] = func
        resp['result'] = None
        resp['id'] = id
        resp['error'] = error_dict 
        return resp
        
    def heartbeat(self, method, id, **kwargs):
        """
        .. py:func:: heartbeat(method, id, **kwargs)
        
        Registers a kiosks IP address, software version and uptime in the datastore.
        
        :param method: The name of the requested json method (should be equal to 'heartbeat')
        :param id: Unused
        :param **kwargs: Keyword arguments to the function. Should have non-unicode keys.
        :rtype dict: The response object. 
        Expects the following in kwargs:
        kiosk-id : The unique kiosk identifier
        uptime: A string representing the time elapsed since the script executed.
        sw_version: Version of the datalogger software running on the device.
        time: The UTC time on the datalogger.
        
        Expects the following headers:
        X-eko-signature: RSA signed hash of request body.
        """
        # extract arguments
        dieid = kwargs['kiosk-id']
        logging.debug('Device with id : %s has a pulse from %s.' % (dieid, self.request.remote_addr))
        
        uptime = kwargs['uptime']
        sw_version = kwargs['sw-version']
        local_inetadr = kwargs['rwanda-ip']
        time = kwargs['time']
        
        # find the kiosk from the kiosk id
        kiosk = Kiosk.kiosk_from_dieid(dieid)
        
        if not kiosk:
            logging.warn('Unrecognised kiosk attempted %s (IP: %s, sw_version: %s, dieid: %s).' % (method, self.request.remote_addr, sw_version, dieid))
            return self._standard_error_response(method, id, 'Not recognised', 20, 'Kiosk not registered with system.')
        
        # log this event
        logging.info('Receiving kiosk heartbeat from kiosk %s at IP %s' % (kiosk.name, self.request.remote_addr))
        
        hash, signature = self._extract_signature_hdr()
       
        if not signature[0]:
            # prepare error response
            return self._standard_error_response(method, id, 'Signature Absent', 10, 'Verification signature not present.')
        
        pubkey = self._get_public_key(kiosk)
        
        if not pubkey:
            return self._standard_error_response(method, id, 'Signature Fail', 11, 'Unable to create public key from config.')
        
        
        if pubkey and signature:
            if pubkey.verify(hash, signature):
                #message is authentic
                self._create_heartbeat(kiosk, uptime, sw_version, time, self.request.remote_addr, local_inetadr)
                data = {}
                data['result'] = 'Success'
                data['error'] = None
                data['id'] = id
                return data
            else:
                return self._standard_error_response(method, id, 'Signature Incorrect', 12, 'Verification signature failed check.')
    
    # registers the heartbeat in the data store
    def _create_heartbeat(self, kiosk, uptime, sw_version, time, ip, local_inetadr):
        """
        .. py:func:: _create_heartbeat(kiosk, uptime, sw_version, time, ip)
        
           Creates a heartbeat record in the datastore.
        """
        h = Heartbeat()
        h.client_ip = ip
        h.client_intip = local_inetadr
        h.client_uptime = uptime
        h.kiosk  = kiosk
        h.software_version = sw_version
        if isinstance(time, datetime):
        	h.client_time = time
        else:
        	h.client_time = datetime.utcnow()
        	logging.warn("Client sent me a non datetime object in the request!")
        h.server_time = datetime.utcnow()
        oldh = memcache.get(kiosk.dieid, namespace='latest-beats')
        # if we have cached a kiosk which has the same IP as this request, just update the cache
        if oldh:
        	if oldh.client_ip != h.client_ip:
        		# if the IP has changed, add to the db
        		if not oldh.is_saved():
        			# old h is the last hb at that address
        			oldh.put()
        		h.put()
        	elif h.bad_skew():
        		# if the skew is worth noting, add to the db
        		logging.debug("Commiting to db for bad skew!")
        		h.put()
    	else:
    		# we dont have anything cached, so add anyway
    		h.put()
        memcache.set(kiosk.dieid, h, namespace='latest-beats')
        logging.debug("Heartbeat registered from %s@%s" % (kiosk.name, ip))
        return
        
    def post_messages(self, method, id, **kwargs):
        """
        .. py:func:: post_messages(method, id, **kwargs)
        
        Registers a message from a kiosk in the datastore.
        
        :param method: The name of the requested json method (should be equal to 'post_messages')
        :param id: Unused
        :param **kwargs: Keyword arguments to the function. Should have non-unicode keys.
        :rtype dict: The response object. 
        
        Expects the following in kwargs:
        kiosk-id : The unique kiosk identifier
        message: A text message.
        origin: The origin of the message (subsystem).
        date: The UTC time on the datalogger that the message originated.
        
        [session-ref] : An optional reference to a upload session.
        
        Expects the following headers:
        X-eko-signature: RSA signed hash of request body.
        """
        # extract arguments
        dieid = kwargs['kiosk-id']
        logging.debug('Device with id : %s has a pulse from %s.' % (dieid, self.request.remote_addr))
        
        messages = kwargs['messages']
        
        # find the kiosk from the kiosk id
        logging.debug('device with id : %s has a pulse.' % dieid)
        kiosk = Kiosk.kiosk_from_dieid(dieid)
        
        
        if not kiosk:
            logging.warn('Unrecognised kiosk attempted %s (IP: %s, dieid: %s).' % (method, self.request.remote_addr, dieid))
            return self._standard_error_response(method, id, 'Not recognised', 20, 'Kiosk not registered with system.')
        
        # log this event
        logging.info('Receiving %d kiosk messages from kiosk %s at IP %s' % (len(messages), kiosk.name, self.request.remote_addr))
        
        hash, signature = self._extract_signature_hdr()
       
        if not signature[0]:
            # prepare error response
            return self._standard_error_response(method, id, 'Signature Absent', 10, 'Verification signature not present.')
        
        pubkey = self._get_public_key(kiosk)
        
        if not pubkey:
            return self._standard_error_response(method, id, 'Signature Fail', 11, 'Unable to create public key from config.')
        
        
        if pubkey and signature:
            if pubkey.verify(hash, signature):
                #message is authentic
                for message in messages:
                    self._create_clientmsg(kiosk, message, self.request.remote_addr)
                data = {}
                data['result'] = 'Success'
                data['error'] = None
                data['id'] = id
                return data
            else:
                return self._standard_error_response(method, id, 'Signature Incorrect', 12, 'Verification signature failed check.')
    
    # registers the heartbeat in the data store
    def _create_clientmsg(self, kiosk, message, ip):
        """
        .. py:func:: _create_clientmsg(kiosk, message)
        
           Creates a kisok message record in the datastore.
        """
        h = KioskMessage()
        h.ip = ip
        h.date = datetime.utcnow()
        h.kiosk  = kiosk
        
        if 'session-ref' in message.keys():
            session_uuid = message['session-ref']
        else:
            session_uuid = None
            
        if 'message' in message.keys():
            if message['message'] is not None:
                h.message = message['message']
        else:
            logging.error("No message in kiosk msg from %s" % kiosk.dieid)
        
        if 'origin' in message.keys():
            if message['origin'] is not None:
                h.origin = message['origin']
        else:
            logging.error("No origin for kiosk msg from %s" % kiosk.dieid)
        
        if 'origin-date' in message.keys():
            if message['origin-date'] is not None:
            	if isinstance(message['origin-date'], datetime):
                	h.origin_date = message['origin-date']
                else:
                	try:
                		h.origin_date = datetime.strptime(message['origin-date'], '%Y-%m-%dT%H:%M:%S')
                	except ValueError:
                		logging.exception("Cannot treat %s as datetime." % message['origin-date'])
        else:
            logging.error("No origin date for kiosk msg from %s" % kiosk.dieid)
        
        if session_uuid is not None:
            sess = db.GqlQuery("SELECT __key__ FROM SyncSession WHERE client_ref = :1", session_uuid).get()
            if sess:
                h.session_ref = sess
        
        h.put()
        logging.info("Kiosk Message registered from %s@%s" % (kiosk.name, ip))
        return
 def post(self):
     
     logging.debug('Running File Upload Handler')
     
     bconv = BaseConverter('0123456789abcdef')
     
     dieid = self.request.get('kiosk-id')
     if not dieid:
         self.error(403)
         self.response.write('No device id provided.\n')
         logger.warn('Device attempted contact without die id from ip %s.' % self.request.remote_addr)
         return
         
     logging.info("File upload incoming from kiosk : %s" % dieid)
     
     kiosk = Kiosk.kiosk_from_dieid(dieid)
     
     if not kiosk:
         self.error(400)
         self.response.write('Kiosk is unregistered on system.\n')
         logging.warn('Unregistered kiosk on ip %s with dieid %s.' % (self.request.remote_addr, dieid))
         return
         
     logging.debug("Encoded sig: %s." % self.request.headers['X-eko-signature'])
     
     # look for the signature
     try:
         signature = bconv.to_decimal(self.request.headers['X-eko-signature'])
         logging.debug("Decoded sig: %s." % str(signature))
         challenge = self.request.headers['X-eko-challenge']
         logging.debug("Challenge: %s." % challenge)
         # signature should be uuid we sent kiosk signed with the public key
         verify = self._verify_client(kiosk, signature, challenge)
     except:
         logging.exception("Authentication signature not found.\n")
         verify = False
     
     # auth failed
     if not verify:
         logging.error("Kiosk id %s did not pass id check." % dieid)
         self.response.write('Unable to verify identity of kiosk.\n')
         return
     
     # a uuid that identifies this data packet on the remote device
     client_ref       = self.request.headers['X-eko-challenge']
     if not client_ref:
         logging.error("No client reference provided.")
         self.response.write('Client Error: No reference provided.')
         return
     
     # the type of data being sent (logs, readings, etc...)
     type             = self.request.get('type')
     # version of the client
     software_version = self.request.get('software_version')
     # manifest
     try:
         manifest = self.get_uploads(field_name='manifest')[0]
         logging.debug("Manifest uploaded, size: %s" % str(manifest.size))
     except:
         logging.exception("Manifest missing from upload data.")
         self.response.write('Manifest Not Found\n')
     
     # payload
     try:
         payload = self.get_uploads(field_name='payload')[0]
     except:
         logging.exception("Payload missing from upload data.")
         self.response.write('Payload Not Found\n')
     
     sync = SyncSession.get_by_clientref(client_ref)
     if not sync:
         logging.error("Upload attempt without requesting sync session.")
         self.response.write('Upload request improperly handled.')
         return
     sync.kiosk = kiosk
     sync.data_type = type
     sync.payload_size = 0
     
     # payload size + manifest size
     if payload:
         sync.payload_size += payload.size
     if manifest:
         sync.payload_size += manifest.size
     
     sync.payload = payload
     sync.manifest = manifest
     
     sync.software_version = software_version
     
     #sync.client_ip = self.request.remote_addr
     sync.end_date = datetime.utcnow()
     
     try:
         sync.put()
         logging.debug("Sync packet succesfully added to datastore")
         self.response.write('Success\n')
     except:
         logging.exception("Adding sync packet failed.")
         self.response.write('Failure\n')
     return