Пример #1
0
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)
Пример #2
0
class GCMRouter(object):
    """GCM Router Implementation"""
    gcm = None
    ttl = 60
    dryRun = 0
    collapseKey = "simplepush"
    creds = {}

    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):
        msg["senderid"] = self.creds.get("senderID")
        return msg

    def register(self, uaid, router_data):
        """Validate that a token is in the ``router_data``"""
        if not router_data.get("token"):
            self._error("connect info missing 'token'", status=401)
        # Assign a senderid
        router_data["creds"] = self.creds = self.senderIDs.choose_ID()
        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-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:
                self._error("%s Content-Encoding" % lead, 400)
            enc = notification.headers.get('encryption', None)
            if not enc:
                self._error("%s Encryption" % lead, 400)
            enckey = notification.headers.get('encryption-key', None)
            if not enckey:
                self._error("%s Encryption-Key" % lead, 400)
            data['body'] = notification.data
            data['con'] = con
            data['enc'] = enc
            data['enckey'] = enckey

        payload = gcmclient.JSONMessage(
            registration_ids=[router_data["token"]],
            collapse_key=self.collapseKey,
            time_to_live=self.ttl,
            dry_run=self.dryRun,
            data=data,
        )
        creds = router_data.get("creds", {"senderID": "missing id"})
        try:
            self.gcm.api_key = creds["auth"]
            result = self.gcm.send(payload)
        except KeyError:
            self._error("Server error, missing bridge credentials for %s" %
                        creds.get("senderID"), 500)
        except gcmclient.GCMAuthenticationError, e:
            self._error("Authentication Error: %s" % e, 500)
        except Exception, e:
            self._error("Unhandled exception in GCM Routing: %s" % e, 500)
Пример #3
0
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)
Пример #4
0
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)
Пример #5
0
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)