class GCMRouter(object): """GCM Router Implementation""" log = Logger() gcm = None dryRun = 0 collapseKey = "simplepush" def __init__(self, ap_settings, router_conf): """Create a new GCM router and connect to GCM""" self.config = router_conf self.min_ttl = router_conf.get("ttl", 60) self.dryRun = router_conf.get("dryrun", False) self.collapseKey = router_conf.get("collapseKey", "simplepush") self.senderIDs = router_conf.get("senderIDs") if not self.senderIDs: self.senderIDs = SenderIDs(router_conf) try: senderID = self.senderIDs.choose_ID() self.gcm = gcmclient.GCM(senderID.get("auth")) except: raise IOError("GCM Bridge not initiated in main") self.log.debug("Starting GCM router...") def check_token(self, token): if token not in self.senderIDs.senderIDs(): return (False, self.senderIDs.choose_ID().get('senderID')) return (True, token) def amend_msg(self, msg, data=None): if data is not None: msg["senderid"] = data.get('creds', {}).get('senderID') return msg def register(self, uaid, router_data, router_token=None, *kwargs): """Validate that the GCM Instance Token is in the ``router_data``""" if "token" not in router_data: raise self._error("connect info missing GCM Instance 'token'", status=401) # Assign a senderid router_data["creds"] = self.senderIDs.get_ID(router_token) return router_data def route_notification(self, notification, uaid_data): """Start the GCM notification routing, returns a deferred""" router_data = uaid_data["router_data"] # Kick the entire notification routing off to a thread return deferToThread(self._route, notification, router_data) def _route(self, notification, router_data): """Blocking GCM call to route the notification""" data = {"chid": notification.channel_id} # Payload data is optional. The endpoint handler validates that the # correct encryption headers are included with the data. if notification.data: mdata = self.config.get('max_data', 4096) if len(notification.data) > mdata: raise self._error("This message is intended for a " + "constrained device and is limited " + "to 3070 bytes. Converted buffer too " + "long by %d bytes" % (len(notification.data) - mdata), 413, errno=104) data['body'] = notification.data data['con'] = notification.headers['content-encoding'] data['enc'] = notification.headers['encryption'] if 'crypto-key' in notification.headers: data['cryptokey'] = notification.headers['crypto-key'] elif 'encryption-key' in notification.headers: data['enckey'] = notification.headers['encryption-key'] # registration_ids are the GCM instance tokens (specified during # registration. router_ttl = notification.ttl or 0 payload = gcmclient.JSONMessage( registration_ids=[router_data.get("token")], collapse_key=self.collapseKey, time_to_live=max(self.min_ttl, router_ttl), dry_run=self.dryRun or ("dryrun" in router_data), data=data, ) creds = router_data.get("creds", {"senderID": "missing id"}) try: self.gcm.api_key = creds["auth"] result = self.gcm.send(payload) except KeyError: raise self._error("Server error, missing bridge credentials " + "for %s" % creds.get("senderID"), 500) except gcmclient.GCMAuthenticationError, e: raise self._error("Authentication Error: %s" % e, 500) except Exception, e: raise self._error("Unhandled exception in GCM Routing: %s" % e, 500)
class GCMRouter(object): """GCM Router Implementation""" gcm = None ttl = 60 dryRun = 0 collapseKey = "simplepush" def __init__(self, ap_settings, router_conf): """Create a new GCM router and connect to GCM""" self.config = router_conf self.ttl = router_conf.get("ttl", 60) self.dryRun = router_conf.get("dryrun", False) self.collapseKey = router_conf.get("collapseKey", "simplepush") self.senderIDs = router_conf.get("senderIDs") if not self.senderIDs: self.senderIDs = SenderIDs(router_conf) try: senderID = self.senderIDs.choose_ID() self.gcm = gcmclient.GCM(senderID.get("auth")) except: raise IOError("GCM Bridge not initiated in main") log.msg("Starting GCM router...") def check_token(self, token): if token not in self.senderIDs.senderIDs(): return (False, self.senderIDs.choose_ID().get('senderID')) return (True, token) def amend_msg(self, msg, data=None): if data is not None: msg["senderid"] = data.get('creds', {}).get('senderID') return msg def register(self, uaid, router_data, router_token=None, *kwargs): """Validate that the GCM Instance Token is in the ``router_data``""" if "token" not in router_data: raise self._error("connect info missing GCM Instance 'token'", status=401) # Assign a senderid router_data["creds"] = self.senderIDs.get_ID(router_token) return router_data def route_notification(self, notification, uaid_data): """Start the GCM notification routing, returns a deferred""" router_data = uaid_data["router_data"] # Kick the entire notification routing off to a thread return deferToThread(self._route, notification, router_data) def _route(self, notification, router_data): """Blocking GCM call to route the notification""" data = {"chid": notification.channel_id, "ver": notification.version} # Payload data is optional. If present, all of Content-Encoding, # Encryption, and Encryption/Crypto-Key are required. If one or # more are missing, a 400 response is produced. if notification.data: lead = "notification with data is missing header:" con = notification.headers.get('content-encoding', None) if not con: raise self._error("%s Content-Encoding" % lead, 400) enc = notification.headers.get('encryption', None) if not enc: raise self._error("%s Encryption" % lead, 400) if ('crypto-key' in notification.headers and 'encryption-key' in notification.headers): raise self._error("notification with data has both" "crypto-key and encryption-key headers", 400) if not ('crypto-key' in notification.headers or 'encryption-key' in notification.headers): raise self._error("notification with data is missing " + "key header", 400) if ('encryption-key' in notification.headers): data['enckey'] = notification.headers.get('encryption-key') if ('crypto-key' in notification.headers): data['cryptokey'] = notification.headers.get('crypto-key') udata = urlsafe_b64encode(notification.data) mdata = self.config.get('max_data', 4096) if len(udata) > mdata: raise self._error("This message is intended for a " + "constrained device and is limited " + "to 3070 bytes. Converted buffer too " + "long by %d bytes" % (len(udata) - mdata), 413, errno=104) # TODO: if the data is longer than max_data, raise error data['body'] = udata data['con'] = con data['enc'] = enc # registration_ids are the GCM instance tokens (specified during # registration. payload = gcmclient.JSONMessage( registration_ids=[router_data.get("token")], collapse_key=self.collapseKey, time_to_live=self.ttl, dry_run=self.dryRun or ("dryrun" in router_data), data=data, ) creds = router_data.get("creds", {"senderID": "missing id"}) try: self.gcm.api_key = creds["auth"] result = self.gcm.send(payload) except KeyError: raise self._error("Server error, missing bridge credentials " + "for %s" % creds.get("senderID"), 500) except gcmclient.GCMAuthenticationError, e: raise self._error("Authentication Error: %s" % e, 500) except Exception, e: raise self._error("Unhandled exception in GCM Routing: %s" % e, 500)
class SenderIDsTestCase(unittest.TestCase): def setUp(self): mock_dynamodb2().start() mock_s3().start() self.senderIDs = None def tearDown(self): mock_dynamodb2().stop() mock_s3().stop() if self.senderIDs: self.senderIDs.stop() def test_nos3(self): self.senderIDs = SenderIDs(dict(use_s3=False)) self.senderIDs.conn = Mock() self.senderIDs._refresh() eq_(self.senderIDs.conn.get_bucket.call_count, 0) def test_bad_init(self): self.senderIDs = SenderIDs(dict(senderid_list="[Update")) eq_(self.senderIDs._senderIDs, {}) def test_success(self): settings = dict( s3_bucket=TEST_BUCKET, senderid_expry=10, senderid_list=test_list, ) self.senderIDs = SenderIDs(settings) eq_(self.senderIDs.conn.get_bucket(settings.get("s3_bucket")). get_key('senderids').get_contents_as_string(), json.dumps(settings.get("senderid_list"))) eq_(self.senderIDs.senderIDs(), settings.get("senderid_list")) # choose_ID may modify the record in memory adding a field. got = self.senderIDs.choose_ID() ok_(got.get('senderID') in settings.get("senderid_list").keys()) ok_(got.get('auth') == settings.get("senderid_list")[got.get('senderID')]['auth']) self.senderIDs._expry = 0 def test_ensureCreated(self): settings = dict( s3_bucket=TEST_BUCKET, senderid_expry=0, senderid_list=test_list, ) self.senderIDs = SenderIDs(settings) oldConn = self.senderIDs.conn self.senderIDs.conn = Mock() self.senderIDs.conn.get_bucket.side_effect = \ [S3ResponseError(404, "Not Found", ""), None] self.senderIDs._create = Mock() def handle_finish(*args): ok_(self.senderIDs._create.called) self.senderIDs.conn = oldConn d = self.senderIDs._refresh() d.addBoth(handle_finish) return d def test_update(self): settings = dict( s3_bucket=TEST_BUCKET, senderid_expry=0, senderid_list=test_list, ) senderIDs = SenderIDs(settings) update = {"test789": {"auth": "ghi"}} senderIDs.update(update) eq_(senderIDs.conn.get_bucket(settings.get("s3_bucket")). get_key('senderids').get_contents_as_string(), json.dumps(update)) return def test_bad_update(self): settings = dict( s3_bucket=TEST_BUCKET, senderid_expry=0, senderid_list=test_list, ) self.senderIDs = SenderIDs(settings) update = {} d = self.senderIDs.update(update) eq_(d, None) eq_(self.senderIDs._senderIDs, test_list) self.senderIDs.update([123]) eq_(d, None) eq_(self.senderIDs._senderIDs, test_list) self.senderIDs.conn.create_bucket(TEST_BUCKET) # Try a valid, but incorrectly formatted set of senderIDs tkey = Key(self.senderIDs.conn.get_bucket(TEST_BUCKET)) tkey.key = self.senderIDs.KEYNAME tkey.set_contents_from_string("[123,456]") self.senderIDs._update_senderIDs() eq_(self.senderIDs._senderIDs, test_list) return def test_get_record(self): settings = dict( s3_bucket=TEST_BUCKET, senderid_expry=0, senderid_list=test_list, ) self.senderIDs = SenderIDs(settings) fetch = self.senderIDs.get_ID('test123') eq_(fetch, {"senderID": "test123", "auth": "abc"}) return self.senderIDs.stop() def test_get_norecord(self): settings = dict( s3_bucket=TEST_BUCKET, senderid_expry=0, ) self.senderIDs = SenderIDs(settings) fetch = self.senderIDs.choose_ID() eq_(fetch, None) return def test_refresh(self): settings = dict( s3_bucket=TEST_BUCKET, senderid_expry=0, senderid_list=test_list, ) self.senderIDs = SenderIDs(settings) self.senderIDs._senderIDs = {} self.senderIDs._expry = 0 twisted.internet.base.DelayedCall.debug = True def finish_handler(*args): eq_(self.senderIDs._senderIDs, test_list) d = self.senderIDs._refresh() d.addBoth(finish_handler) return d @patch("autopush.senderids.LoopingCall", spec=twisted.internet.task.LoopingCall) def test_start(self, fts): settings = dict( s3_bucket=TEST_BUCKET, senderid_expry=0, senderid_list=test_list, ) self.senderIDs = SenderIDs(settings) self.senderIDs.start() ok_(self.senderIDs.service.start.called) fts.running = True self.senderIDs.stop() ok_(self.senderIDs.service.stop.called)
class GCMRouter(object): """GCM Router Implementation""" log = Logger() gcm = None dryRun = 0 collapseKey = "simplepush" def __init__(self, ap_settings, router_conf): """Create a new GCM router and connect to GCM""" self.config = router_conf self.min_ttl = router_conf.get("ttl", 60) self.dryRun = router_conf.get("dryrun", False) self.collapseKey = router_conf.get("collapseKey", "simplepush") self.senderIDs = router_conf.get("senderIDs") if not self.senderIDs: self.senderIDs = SenderIDs(router_conf) try: senderID = self.senderIDs.choose_ID() self.gcm = gcmclient.GCM(senderID.get("auth")) except: raise IOError("GCM Bridge not initiated in main") self.log.debug("Starting GCM router...") def check_token(self, token): if token not in self.senderIDs.senderIDs(): return (False, self.senderIDs.choose_ID().get('senderID')) return (True, token) def amend_msg(self, msg, data=None): if data is not None: msg["senderid"] = data.get('creds', {}).get('senderID') return msg def register(self, uaid, router_data, router_token=None, *kwargs): """Validate that the GCM Instance Token is in the ``router_data``""" if "token" not in router_data: raise self._error("connect info missing GCM Instance 'token'", status=401) # Assign a senderid router_data["creds"] = self.senderIDs.get_ID(router_token) return router_data def route_notification(self, notification, uaid_data): """Start the GCM notification routing, returns a deferred""" router_data = uaid_data["router_data"] # Kick the entire notification routing off to a thread return deferToThread(self._route, notification, router_data) def _route(self, notification, router_data): """Blocking GCM call to route the notification""" data = {"chid": notification.channel_id} # Payload data is optional. The endpoint handler validates that the # correct encryption headers are included with the data. if notification.data: mdata = self.config.get('max_data', 4096) if len(notification.data) > mdata: raise self._error("This message is intended for a " + "constrained device and is limited " + "to 3070 bytes. Converted buffer too " + "long by %d bytes" % (len(notification.data) - mdata), 413, errno=104) data['body'] = notification.data data['con'] = notification.headers['content-encoding'] data['enc'] = notification.headers['encryption'] if 'crypto-key' in notification.headers: data['cryptokey'] = notification.headers['crypto-key'] elif 'encryption-key' in notification.headers: data['enckey'] = notification.headers['encryption-key'] # registration_ids are the GCM instance tokens (specified during # registration. router_ttl = notification.ttl or 0 payload = gcmclient.JSONMessage( registration_ids=[router_data.get("token")], collapse_key=self.collapseKey, time_to_live=max(self.min_ttl, router_ttl), dry_run=self.dryRun or ("dryrun" in router_data), data=data, ) creds = router_data.get("creds", {"senderID": "missing id"}) try: self.gcm.api_key = creds["auth"] result = self.gcm.send(payload) except KeyError: raise self._error( "Server error, missing bridge credentials " + "for %s" % creds.get("senderID"), 500) except gcmclient.GCMAuthenticationError, e: raise self._error("Authentication Error: %s" % e, 500) except Exception, e: raise self._error("Unhandled exception in GCM Routing: %s" % e, 500)