Beispiel #1
0
def make_settings(args, **kwargs):
    """Helper function to make a :class:`AutopushSettings` object"""
    router_conf = {}
    if args.key_hash:
        db.key_hash = args.key_hash
    # Some routers require a websocket to timeout on idle (e.g. UDP)
    if args.wake_pem is not None and args.wake_timeout != 0:
        router_conf["simplepush"] = {"idle": args.wake_timeout,
                                     "server": args.wake_server,
                                     "cert": args.wake_pem}
    if args.apns_enabled:
        # if you have the critical elements for each external router, create it
        if args.apns_cert_file is not None and args.apns_key_file is not None:
            router_conf["apns"] = {"sandbox": args.apns_sandbox,
                                   "cert_file": args.apns_cert_file,
                                   "key_file": args.apns_key_file}
    if args.gcm_enabled:
        # Create a common gcmclient
        slist = json.loads(args.senderid_list)
        senderIDs = SenderIDs(dict(
            s3_bucket=args.s3_bucket,
            senderid_expry=args.senderid_expry,
            use_s3=args.s3_bucket.lower() != "none",
            senderid_list=slist))
        # This is an init check to verify that things are configured
        # correctly. Otherwise errors may creep in later that go
        # unaccounted.
        senderID = senderIDs.choose_ID()
        if senderID is None:
            log.err("No GCM SenderIDs specified or found.")
            return
        router_conf["gcm"] = {"ttl": args.gcm_ttl,
                              "dryrun": args.gcm_dryrun,
                              "max_data": args.max_data,
                              "collapsekey": args.gcm_collapsekey,
                              "senderIDs": senderIDs,
                              "senderid_list": list}

    return AutopushSettings(
        crypto_key=args.crypto_key,
        datadog_api_key=args.datadog_api_key,
        datadog_app_key=args.datadog_app_key,
        datadog_flush_interval=args.datadog_flush_interval,
        hostname=args.hostname,
        statsd_host=args.statsd_host,
        statsd_port=args.statsd_port,
        router_conf=router_conf,
        router_tablename=args.router_tablename,
        storage_tablename=args.storage_tablename,
        storage_read_throughput=args.storage_read_throughput,
        storage_write_throughput=args.storage_write_throughput,
        message_tablename=args.message_tablename,
        message_read_throughput=args.message_read_throughput,
        message_write_throughput=args.message_write_throughput,
        router_read_throughput=args.router_read_throughput,
        router_write_throughput=args.router_write_throughput,
        resolve_hostname=args.resolve_hostname,
        wake_timeout=args.wake_timeout,
        **kwargs
    )
Beispiel #2
0
 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
Beispiel #3
0
 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()
Beispiel #4
0
 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)
Beispiel #5
0
    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
Beispiel #6
0
 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...")
Beispiel #7
0
    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
Beispiel #8
0
    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
Beispiel #9
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
Beispiel #10
0
 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
Beispiel #11
0
 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...")
Beispiel #12
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 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.
        con = notification.headers.get('content-encoding', None)
        enc = notification.headers.get('encryption', None)
        enckey = notification.headers.get('encryption-key', None)
        if notification.data:
            if not con:
                self._error("notification with data is missing header: Content-Encoding", 400)
            if not enc:
                self._error("notification with data is missing header: Encryption", 400)
            if not enckey:
                self._error("notification with data is missing header: Encryption-Key", 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)
Beispiel #13
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)
Beispiel #14
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)
Beispiel #15
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)
Beispiel #16
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 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"""
        payload = gcmclient.JSONMessage(
            registration_ids=[router_data["token"]],
            collapse_key=self.collapseKey,
            time_to_live=self.ttl,
            dry_run=self.dryRun,
            data={"Msg": notification.data,
                  "Chid": notification.channel_id,
                  "Ver": notification.version}
        )
        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)
Beispiel #17
0
def make_settings(args, **kwargs):
    """Helper function to make a :class:`AutopushSettings` object"""
    router_conf = {}
    if args.key_hash:
        db.key_hash = args.key_hash
    # Some routers require a websocket to timeout on idle (e.g. UDP)
    if args.wake_pem is not None and args.wake_timeout != 0:
        router_conf["simplepush"] = {
            "idle": args.wake_timeout,
            "server": args.wake_server,
            "cert": args.wake_pem
        }
    if args.apns_enabled:
        # if you have the critical elements for each external router, create it
        if args.apns_cert_file is not None and args.apns_key_file is not None:
            router_conf["apns"] = {
                "sandbox": args.apns_sandbox,
                "cert_file": args.apns_cert_file,
                "key_file": args.apns_key_file
            }
    if args.gcm_enabled:
        # Create a common gcmclient
        slist = json.loads(args.senderid_list)
        senderIDs = SenderIDs(
            dict(s3_bucket=args.s3_bucket,
                 senderid_expry=args.senderid_expry,
                 use_s3=args.s3_bucket.lower() != "none",
                 senderid_list=slist))
        # This is an init check to verify that things are configured
        # correctly. Otherwise errors may creep in later that go
        # unaccounted.
        senderID = senderIDs.choose_ID()
        if senderID is None:
            log.critical(format="No GCM SenderIDs specified or found.")
            return
        router_conf["gcm"] = {
            "ttl": args.gcm_ttl,
            "dryrun": args.gcm_dryrun,
            "max_data": args.max_data,
            "collapsekey": args.gcm_collapsekey,
            "senderIDs": senderIDs,
            "senderid_list": list
        }

    return AutopushSettings(
        crypto_key=args.crypto_key,
        datadog_api_key=args.datadog_api_key,
        datadog_app_key=args.datadog_app_key,
        datadog_flush_interval=args.datadog_flush_interval,
        hostname=args.hostname,
        statsd_host=args.statsd_host,
        statsd_port=args.statsd_port,
        router_conf=router_conf,
        router_tablename=args.router_tablename,
        storage_tablename=args.storage_tablename,
        storage_read_throughput=args.storage_read_throughput,
        storage_write_throughput=args.storage_write_throughput,
        message_tablename=args.message_tablename,
        message_read_throughput=args.message_read_throughput,
        message_write_throughput=args.message_write_throughput,
        router_read_throughput=args.router_read_throughput,
        router_write_throughput=args.router_write_throughput,
        resolve_hostname=args.resolve_hostname,
        wake_timeout=args.wake_timeout,
        **kwargs)
Beispiel #18
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"):
            raise 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/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

        payload = gcmclient.JSONMessage(
            registration_ids=[router_data["token"]],
            collapse_key=self.collapseKey,
            time_to_live=self.ttl,
            dry_run=self.dryRun,
            data=udata,
        )
        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)
Beispiel #19
0
 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)
Beispiel #20
0
 def test_bad_init(self):
     self.senderIDs = SenderIDs(dict(senderid_list="[Update"))
     eq_(self.senderIDs._senderIDs, {})