Exemple #1
0
    def test_ttl_batch_partly_expired_and_good_one(self):
        data = str(uuid.uuid4())
        data1 = str(uuid.uuid4())
        data2 = str(uuid.uuid4())
        client = yield self.quick_register()
        yield client.disconnect()
        for x in range(0, 6):
            yield client.send_notification(data=data, status=201)

        for x in range(0, 6):
            yield client.send_notification(data=data1, ttl=1, status=201)

        yield client.send_notification(data=data2, status=201)
        time.sleep(1)
        yield client.connect()
        yield client.hello()

        # Pull out and ack the first
        for x in range(0, 6):
            result = yield client.get_notification(timeout=4)
            assert result is not None
            assert result["data"] == base64url_encode(data)
            yield client.ack(result["channelID"], result["version"])

        # Should have one more that is data2, this will only arrive if the
        # other six were acked as that hits the batch size
        result = yield client.get_notification(timeout=4)
        assert result is not None
        assert result["data"] == base64url_encode(data2)

        # No more
        result = yield client.get_notification()
        assert result is None
        yield self.shut_down(client)
Exemple #2
0
 def test_repeat_delivery_with_disconnect_without_ack(self):
     data = str(uuid.uuid4())
     client = yield self.quick_register()
     result = yield client.send_notification(data=data)
     assert result != {}
     assert result["data"] == base64url_encode(data)
     yield client.disconnect()
     yield client.connect()
     yield client.hello()
     result = yield client.get_notification()
     assert result != {}
     assert result["data"] == base64url_encode(data)
     yield self.shut_down(client)
    def test_post_with_app_server_key(self, *args):
        self.patch('uuid.uuid4', return_value=dummy_chid)

        dummy_key = "RandomKeyString"

        def mock_encrypt(cleartext):
            assert len(cleartext) == 64
            # dummy_uaid
            assert cleartext[0:16] == (
                'abad1dea00000000aabbccdd00000000'.decode('hex'))
            # dummy_chid
            assert cleartext[16:32] == (
                'deadbeef00000000decafbad00000000'.decode('hex'))
            # sha256(dummy_key).digest()
            assert cleartext[32:] == (
                '47aedd050b9e19171f0fa7b8b65ca670'
                '28f0bc92cd3f2cd3682b1200ec759007').decode('hex')
            return 'abcd123'
        self.fernet_mock.configure_mock(**{
            'encrypt.side_effect': mock_encrypt,
        })

        resp = yield self.client.post(
            self.url(router_type="webpush", uaid=dummy_uaid.hex) +
            "/subscription",
            headers={"Authorization": self.auth},
            body=json.dumps(dict(
                type="webpush",
                key=utils.base64url_encode(dummy_key),
                data={},
            ))
        )
        payload = json.loads(resp.content)
        assert payload["channelID"] == dummy_chid.hex
        assert payload["endpoint"] == "http://localhost/wpush/v2/abcd123"
Exemple #4
0
    def fixup_output(self, d):
        # Verify authorization
        # Note: This has to be done here, since schema validation takes place
        #       before nested schemas, and in this case we need all the nested
        #       schema logic to run first.
        self.validate_auth(d)

        # Merge crypto headers back in
        if d["crypto_headers"]:
            d["headers"].update({
                k.replace("_", "-"): v
                for k, v in d["crypto_headers"].items()
            })

        # Base64-encode data for Web Push
        d["body"] = base64url_encode(d["body"])

        # Set the notification based on the validated request schema data
        d["notification"] = WebPushNotification.from_webpush_request_schema(
            data=d,
            fernet=self.context["settings"].fernet,
            legacy=self.context["settings"]._notification_legacy,
        )

        return d
Exemple #5
0
 def test_basic_delivery(self):
     data = str(uuid.uuid4())
     client = yield self.quick_register(use_webpush=True)
     result = yield client.send_notification(data=data)
     eq_(result["headers"]["encryption"], client._crypto_key)
     eq_(result["data"], base64url_encode(data))
     eq_(result["messageType"], "notification")
     yield self.shut_down(client)
Exemple #6
0
 def _store_auth(self, jwt, crypto_key, token, result):
     if jwt.get('exp', 0) < time.time():
         raise VapidAuthException("Invalid bearer token: Auth expired")
     jwt_crypto_key = base64url_encode(crypto_key)
     self._client_info["jwt_crypto_key"] = jwt_crypto_key
     for i in jwt:
         self._client_info["jwt_" + i] = jwt[i]
     return result
Exemple #7
0
 def _store_auth(self, jwt, crypto_key, token, result):
     if jwt.get('exp', 0) < time.time():
         raise VapidAuthException("Invalid bearer token: Auth expired")
     jwt_crypto_key = base64url_encode(crypto_key)
     self._client_info["jwt_crypto_key"] = jwt_crypto_key
     for i in jwt:
         self._client_info["jwt_" + i] = jwt[i]
     return result
Exemple #8
0
 def test_ttl_0_connected(self):
     data = str(uuid.uuid4())
     client = yield self.quick_register(use_webpush=True)
     result = yield client.send_notification(data=data, ttl=0)
     assert(result is not None)
     eq_(result["headers"]["encryption"], client._crypto_key)
     eq_(result["data"], base64url_encode(data))
     eq_(result["messageType"], "notification")
     yield self.shut_down(client)
Exemple #9
0
 def test_basic_delivery_v0_endpoint(self):
     data = str(uuid.uuid4())
     client = yield self.quick_register(use_webpush=True)
     endpoint = self._make_v0_endpoint(
         client.uaid, client.channels.keys()[0])
     result = yield client.send_notification(endpoint=endpoint, data=data)
     eq_(result["headers"]["encryption"], client._crypto_key)
     eq_(result["data"], base64url_encode(data))
     eq_(result["messageType"], "notification")
     yield self.shut_down(client)
Exemple #10
0
    def test_delivery_repeat_without_ack(self):
        data = str(uuid.uuid4())
        client = yield self.quick_register(use_webpush=True)
        yield client.disconnect()
        ok_(client.channels)
        yield client.send_notification(data=data)
        yield client.connect()
        yield client.hello()
        result = yield client.get_notification()
        ok_(result != {})
        eq_(result["data"], base64url_encode(data))

        yield client.disconnect()
        yield client.connect()
        yield client.hello()
        result = yield client.get_notification()
        ok_(result != {})
        eq_(result["data"], base64url_encode(data))
        yield self.shut_down(client)
Exemple #11
0
 def test_topic_basic_delivery(self):
     data = str(uuid.uuid4())
     client = yield self.quick_register()
     result = yield client.send_notification(data=data, topic="Inbox")
     # the following presumes that only `salt` is padded.
     clean_header = client._crypto_key.replace(
         '"', '').rstrip('=')
     assert result["headers"]["encryption"] == clean_header
     assert result["data"] == base64url_encode(data)
     assert result["messageType"] == "notification"
     yield self.shut_down(client)
Exemple #12
0
    def validate_auth(self, d):
        auth = d["headers"].get("authorization")
        needs_auth = d["token_info"]["api_ver"] == "v2"
        if not needs_auth and not auth:
            return
        try:
            vapid_auth = parse_auth_header(auth)
            token = vapid_auth['t']
            d["vapid_version"] = "draft{:0>2}".format(vapid_auth['version'])
            if vapid_auth['version'] == 2:
                public_key = vapid_auth['k']
            else:
                public_key = d["subscription"].get("public_key")
            jwt = extract_jwt(
                token,
                public_key,
                is_trusted=self.context['settings'].enable_tls_auth)
        except (KeyError, ValueError, InvalidSignature, TypeError,
                VapidAuthException):
            raise InvalidRequest("Invalid Authorization Header",
                                 status_code=401,
                                 errno=109,
                                 headers={"www-authenticate": PREF_SCHEME})
        if "exp" not in jwt:
            raise InvalidRequest("Invalid bearer token: No expiration",
                                 status_code=401,
                                 errno=109,
                                 headers={"www-authenticate": PREF_SCHEME})

        try:
            jwt_expires = int(jwt['exp'])
        except ValueError:
            raise InvalidRequest("Invalid bearer token: Invalid expiration",
                                 status_code=401,
                                 errno=109,
                                 headers={"www-authenticate": PREF_SCHEME})

        now = time.time()
        jwt_has_expired = now > jwt_expires
        if jwt_has_expired:
            raise InvalidRequest("Invalid bearer token: Auth expired",
                                 status_code=401,
                                 errno=109,
                                 headers={"www-authenticate": PREF_SCHEME})
        jwt_too_far_in_future = (jwt_expires - now) > (60 * 60 * 24)
        if jwt_too_far_in_future:
            raise InvalidRequest(
                "Invalid bearer token: Auth > 24 hours in "
                "the future",
                status_code=401,
                errno=109,
                headers={"www-authenticate": PREF_SCHEME})
        jwt_crypto_key = base64url_encode(public_key)
        d["jwt"] = dict(jwt_crypto_key=jwt_crypto_key, jwt_data=jwt)
Exemple #13
0
 def test_basic_delivery_with_vapid(self):
     data = str(uuid.uuid4())
     client = yield self.quick_register()
     vapid_info = _get_vapid(
         payload=self.vapid_payload)
     result = yield client.send_notification(data=data, vapid=vapid_info)
     # the following presumes that only `salt` is padded.
     clean_header = client._crypto_key.replace(
         '"', '').rstrip('=')
     assert result["headers"]["encryption"] == clean_header
     assert result["data"] == base64url_encode(data)
     assert result["messageType"] == "notification"
     yield self.shut_down(client)
Exemple #14
0
def _get_vapid(key=None, payload=None):
    if not payload:
        payload = {"aud": "https://pusher_origin.example.com",
                   "exp": int(time.time()) + 86400,
                   "sub": "mailto:[email protected]"}
    if not key:
        key = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p)
    vk = key.get_verifying_key()
    auth = jws.sign(payload, key, algorithm="ES256").strip('=')
    crypto_key = base64url_encode('\4' + vk.to_string())
    return {"auth": auth,
            "crypto-key": crypto_key,
            "key": key}
Exemple #15
0
    def test_multiple_delivery_with_single_ack(self):
        data = str(uuid.uuid4())
        data2 = str(uuid.uuid4())
        client = yield self.quick_register()
        yield client.disconnect()
        assert client.channels
        yield client.send_notification(data=data, status=201)
        yield client.send_notification(data=data2, status=201)
        yield client.connect()
        yield client.hello()
        result = yield client.get_notification(timeout=0.5)
        assert result != {}
        assert result["data"] == base64url_encode(data)
        result2 = yield client.get_notification(timeout=0.5)
        assert result2 != {}
        assert result2["data"] == base64url_encode(data2)
        yield client.ack(result["channelID"], result["version"])

        yield client.disconnect()
        yield client.connect()
        yield client.hello()
        result = yield client.get_notification(timeout=0.5)
        assert result != {}
        assert result["data"] == base64url_encode(data)
        assert result["messageType"] == "notification"
        result2 = yield client.get_notification()
        assert result2 != {}
        assert result2["data"] == base64url_encode(data2)
        yield client.ack(result["channelID"], result["version"])
        yield client.ack(result2["channelID"], result2["version"])

        # Verify no messages are delivered
        yield client.disconnect()
        yield client.connect()
        yield client.hello()
        result = yield client.get_notification(timeout=0.5)
        assert result is None
        yield self.shut_down(client)
Exemple #16
0
 def test_topic_expired(self):
     data = str(uuid.uuid4())
     client = yield self.quick_register()
     yield client.disconnect()
     assert client.channels
     yield client.send_notification(data=data, ttl=1, topic="test", status=201)
     yield client.sleep(2)
     yield client.connect()
     yield client.hello()
     result = yield client.get_notification(timeout=0.5)
     assert result is None
     result = yield client.send_notification(data=data, topic="test")
     assert result != {}
     assert result["data"] == base64url_encode(data)
     yield self.shut_down(client)
Exemple #17
0
    def test_no_delivery_to_unregistered(self):
        data = str(uuid.uuid4())
        client = yield self.quick_register(use_webpush=True)
        ok_(client.channels)
        chan = client.channels.keys()[0]

        result = yield client.send_notification(data=data)
        eq_(result["channelID"], chan)
        eq_(result["data"], base64url_encode(data))
        yield client.ack(result["channelID"], result["version"])

        yield client.unregister(chan)
        result = yield client.send_notification(data=data, status=404)
        eq_(result, None)
        yield self.shut_down(client)
Exemple #18
0
    def test_post_with_app_server_key(self):
        dummy_key = "RandomKeyString"
        self.reg.request.body = json.dumps(dict(
            type="simplepush",
            key=utils.base64url_encode(dummy_key),
            data={},
        ))

        def mock_encrypt(cleartext):
            eq_(len(cleartext), 64)
            # dummy_uaid
            eq_(cleartext[0:16],
                'abad1dea00000000aabbccdd00000000'.decode('hex'))
            # dummy_chid
            eq_(cleartext[16:32],
                'deadbeef00000000decafbad00000000'.decode('hex'))
            # sha256(dummy_key).digest()
            eq_(cleartext[32:],
                ('47aedd050b9e19171f0fa7b8b65ca670'
                 '28f0bc92cd3f2cd3682b1200ec759007').decode('hex'))
            return 'abcd123'
        self.fernet_mock.configure_mock(**{
            'encrypt.side_effect': mock_encrypt,
        })
        self.reg.request.headers["Authorization"] = self.auth

        def handle_finish(value):
            call_args = self.reg.write.call_args
            ok_(call_args is not None)
            args = call_args[0]
            call_arg = json.loads(args[0])
            eq_(call_arg["channelID"], dummy_chid.hex)
            eq_(call_arg["endpoint"], "http://localhost/wpush/v2/abcd123")

        def restore(*args, **kwargs):
            uuid.uuid4 = old_func

        old_func = uuid.uuid4
        uuid.uuid4 = lambda: dummy_chid
        self.finish_deferred.addBoth(restore)
        self.finish_deferred.addCallback(handle_finish)
        self.reg.request.headers["Authorization"] = self.auth
        self.reg.post(self._make_req(router_type="simplepush",
                                     uaid=dummy_uaid.hex))
        return self.finish_deferred
Exemple #19
0
 def test_topic_replacement_delivery(self):
     data = str(uuid.uuid4())
     data2 = str(uuid.uuid4())
     client = yield self.quick_register()
     yield client.disconnect()
     yield client.send_notification(data=data, topic="Inbox", status=201)
     yield client.send_notification(data=data2, topic="Inbox", status=201)
     yield client.connect()
     yield client.hello()
     result = yield client.get_notification()
     # the following presumes that only `salt` is padded.
     clean_header = client._crypto_key.replace(
         '"', '').rstrip('=')
     assert result["headers"]["encryption"] == clean_header
     assert result["data"] == base64url_encode(data2)
     assert result["messageType"] == "notification"
     result = yield client.get_notification()
     assert result is None
     yield self.shut_down(client)
Exemple #20
0
    def test_no_delivery_to_unregistered(self):
        data = str(uuid.uuid4())
        client = yield self.quick_register()  # type: Client
        assert client.channels
        chan = client.channels.keys()[0]

        result = yield client.send_notification(data=data)
        assert result["channelID"] == chan
        assert result["data"] == base64url_encode(data)
        yield client.ack(result["channelID"], result["version"])

        yield client.unregister(chan)
        result = yield client.send_notification(data=data, status=410)

        # Verify cache-control
        assert client.notif_response.getheader("Cache-Control") == \
            "max-age=86400"

        assert result is None
        yield self.shut_down(client)
Exemple #21
0
    def test_no_delivery_to_unregistered_on_reconnect(self):
        data = str(uuid.uuid4())
        client = yield self.quick_register(use_webpush=True)
        yield client.disconnect()
        ok_(client.channels)
        chan = client.channels.keys()[0]
        yield client.send_notification(data=data)
        yield client.connect()
        yield client.hello()
        result = yield client.get_notification()
        eq_(result["channelID"], chan)
        eq_(result["data"], base64url_encode(data))

        yield client.unregister(chan)
        yield client.disconnect()
        time.sleep(1)
        yield client.connect()
        yield client.hello()
        result = yield client.get_notification()
        eq_(result, None)
        yield self.shut_down(client)
Exemple #22
0
    def test_ttl_batch_expired_and_good_one(self):
        data = str(uuid.uuid4())
        data2 = str(uuid.uuid4())
        client = yield self.quick_register()
        yield client.disconnect()
        for x in range(0, 12):
            yield client.send_notification(data=data, ttl=1, status=201)

        yield client.send_notification(data=data2, status=201)
        time.sleep(1)
        yield client.connect()
        yield client.hello()
        result = yield client.get_notification(timeout=4)
        assert result is not None
        # the following presumes that only `salt` is padded.
        clean_header = client._crypto_key.replace(
            '"', '').rstrip('=')
        assert result["headers"]["encryption"] == clean_header
        assert result["data"] == base64url_encode(data2)
        assert result["messageType"] == "notification"
        result = yield client.get_notification(timeout=0.5)
        assert result is None
        yield self.shut_down(client)
Exemple #23
0
def _get_vapid(key=None, payload=None, endpoint=None):
    global CONNECTION_CONFIG

    if endpoint is None:
        endpoint = "{}://{}:{}".format(
                        CONNECTION_CONFIG.get("endpoint_scheme"),
                        CONNECTION_CONFIG.get("endpoint_hostname"),
                        CONNECTION_CONFIG.get("endpoint_port"),
                   )
    if not payload:
        payload = {"aud": endpoint,
                   "exp": int(time.time()) + 86400,
                   "sub": "mailto:[email protected]"}
    if not payload.get("aud"):
        payload['aud'] = endpoint
    if not key:
        key = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p)
    vk = key.get_verifying_key()
    auth = jws.sign(payload, key, algorithm="ES256").strip('=')
    crypto_key = base64url_encode('\4' + vk.to_string())
    return {"auth": auth,
            "crypto-key": crypto_key,
            "key": key}
Exemple #24
0
 def _gen_jwt(self, header, payload):
     sk256p = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p)
     vk = sk256p.get_verifying_key()
     sig = jws.sign(payload, sk256p, algorithm="ES256").strip('=')
     crypto_key = utils.base64url_encode(vk.to_string()).strip('=')
     return sig, crypto_key
Exemple #25
0
    def validate_auth(self, d):
        auth = d["headers"].get("authorization")
        needs_auth = d["token_info"]["api_ver"] == "v2"
        if not auth and not needs_auth:
            return

        public_key = d["subscription"].get("public_key")
        try:
            auth_type, token = auth.split(' ', 1)
        except ValueError:
            raise InvalidRequest("Invalid Authorization Header",
                                 status_code=401,
                                 errno=109,
                                 headers={"www-authenticate": PREF_SCHEME})

        # If its not a bearer token containing what may be JWT, stop
        if auth_type.lower() not in AUTH_SCHEMES or '.' not in token:
            if needs_auth:
                raise InvalidRequest("Missing Authorization Header",
                                     status_code=401,
                                     errno=109)
            return

        try:
            jwt = extract_jwt(token, public_key)
        except (ValueError, InvalidSignature, TypeError):
            raise InvalidRequest("Invalid Authorization Header",
                                 status_code=401,
                                 errno=109,
                                 headers={"www-authenticate": PREF_SCHEME})
        if "exp" not in jwt:
            raise InvalidRequest("Invalid bearer token: No expiration",
                                 status_code=401,
                                 errno=109,
                                 headers={"www-authenticate": PREF_SCHEME})

        try:
            jwt_expires = int(jwt['exp'])
        except ValueError:
            raise InvalidRequest("Invalid bearer token: Invalid expiration",
                                 status_code=401,
                                 errno=109,
                                 headers={"www-authenticate": PREF_SCHEME})

        now = time.time()
        jwt_has_expired = now > jwt_expires
        if jwt_has_expired:
            raise InvalidRequest("Invalid bearer token: Auth expired",
                                 status_code=401,
                                 errno=109,
                                 headers={"www-authenticate": PREF_SCHEME})
        jwt_too_far_in_future = (jwt_expires - now) > (60 * 60 * 24)
        if jwt_too_far_in_future:
            raise InvalidRequest(
                "Invalid bearer token: Auth > 24 hours in "
                "the future",
                status_code=401,
                errno=109,
                headers={"www-authenticate": PREF_SCHEME})
        jwt_crypto_key = base64url_encode(public_key)
        d["jwt"] = dict(jwt_crypto_key=jwt_crypto_key, jwt_data=jwt)
Exemple #26
0
    def validate_auth(self, d):
        crypto_exceptions = [
            KeyError, ValueError, TypeError, VapidAuthException
        ]

        if self.context['conf'].use_cryptography:
            crypto_exceptions.append(InvalidSignature)
        else:
            crypto_exceptions.extend([JOSEError, JWTError, AssertionError])

        auth = d["headers"].get("authorization")
        needs_auth = d["token_info"]["api_ver"] == "v2"
        if not needs_auth and not auth:
            return
        try:
            vapid_auth = parse_auth_header(auth)
            token = vapid_auth['t']
            d["vapid_version"] = "draft{:0>2}".format(vapid_auth['version'])
            if vapid_auth['version'] == 2:
                public_key = vapid_auth['k']
            else:
                public_key = d["subscription"].get("public_key")
            jwt = extract_jwt(token,
                              public_key,
                              is_trusted=self.context['conf'].enable_tls_auth,
                              use_crypto=self.context['conf'].use_cryptography)
            if not isinstance(jwt, Dict):
                raise InvalidRequest("Invalid Authorization Header",
                                     status_code=401,
                                     errno=109,
                                     headers={"www-authenticate": PREF_SCHEME})
        except tuple(crypto_exceptions):
            raise InvalidRequest("Invalid Authorization Header",
                                 status_code=401,
                                 errno=109,
                                 headers={"www-authenticate": PREF_SCHEME})
        if "aud" not in jwt:
            raise InvalidRequest("Invalid bearer token: No Audience specified",
                                 status_code=401,
                                 errno=109,
                                 headers={"www-authenticate": PREF_SCHEME})
        if jwt['aud'] != self.context["conf"].endpoint_url:
            raise InvalidRequest(
                "Invalid bearer token: Invalid Audience Specified",
                status_code=401,
                errno=109,
                headers={"www-authenticate": PREF_SCHEME})
        if "exp" not in jwt:
            raise InvalidRequest("Invalid bearer token: No expiration",
                                 status_code=401,
                                 errno=109,
                                 headers={"www-authenticate": PREF_SCHEME})

        try:
            jwt_expires = int(jwt['exp'])
        except (TypeError, ValueError):
            raise InvalidRequest("Invalid bearer token: Invalid expiration",
                                 status_code=401,
                                 errno=109,
                                 headers={"www-authenticate": PREF_SCHEME})

        now = time.time()
        jwt_has_expired = now > jwt_expires
        if jwt_has_expired:
            raise InvalidRequest("Invalid bearer token: Auth expired",
                                 status_code=401,
                                 errno=109,
                                 headers={"www-authenticate": PREF_SCHEME})
        jwt_too_far_in_future = (jwt_expires - now) > (60 * 60 * 24)
        if jwt_too_far_in_future:
            raise InvalidRequest(
                "Invalid bearer token: Auth > 24 hours in "
                "the future",
                status_code=401,
                errno=109,
                headers={"www-authenticate": PREF_SCHEME})
        jwt_crypto_key = base64url_encode(public_key)
        d["jwt"] = dict(jwt_crypto_key=jwt_crypto_key, jwt_data=jwt)
Exemple #27
0
    def _uaid_lookup_results(self, result):
        """Process the result of the AWS UAID lookup"""
        # Save the whole record
        router_key = self.router_key = result.get("router_type", "simplepush")
        self._client_info["router_key"] = router_key

        try:
            self.router = self.ap_settings.routers[router_key]
        except KeyError:
            self.log.debug(
                format="Invalid router requested", status_code=400,
                errno=108, **self._client_info)
            return self._write_response(400, 108,
                                        message="Invalid router")

        # Only simplepush uses version/data out of body/query, GCM/APNS will
        # use data out of the request body 'WebPush' style.
        use_simplepush = router_key == "simplepush"
        if use_simplepush:
            self.version, data = parse_request_params(self.request)
            self._client_info['message_id'] = self.version
        else:
            data = self.request.body
            if "ttl" not in self.request.headers:
                ttl = None
            # We need crypto headers for messages with payloads.
            req_fields = ["content-encoding", "encryption"]
            if data and not all([x in self.request.headers
                                 for x in req_fields]):
                self.log.debug(format="Client error", status_code=400,
                               errno=101, **self._client_info)
                return self._write_response(400, 101)
            if ("encryption-key" in self.request.headers and
                    "crypto-key" in self.request.headers):
                self.log.debug(format="Client error", status_code=400,
                               errno=110, **self._client_info)
                return self._write_response(
                    400, 110, message="Invalid crypto headers")
            self._client_info["message_size"] = len(data) if data else 0

        if "ttl" not in self.request.headers:
            ttl = None
        elif VALID_TTL.match(self.request.headers["ttl"]):
            ttl = int(self.request.headers["ttl"])
            # Cap the TTL to our MAX_TTL
            ttl = min(ttl, MAX_TTL)
        else:
            self.log.debug(format="Client error", status_code=400,
                           errno=112, **self._client_info)
            return self._write_response(400, 112, message="Invalid TTL header")

        if data and len(data) > self.ap_settings.max_data:
            self.log.debug(format="Client error", status_code=400, errno=104,
                           **self._client_info)
            return self._write_response(
                413, 104, message="Data payload too large")

        if use_simplepush:
            self._route_notification(self.version, result, data)
            return

        # Web Push and bridged messages are encrypted binary blobs. We store
        # and deliver these messages as Base64-encoded strings.
        data = base64url_encode(self.request.body)

        # Generate a message ID, then route the notification.
        d = deferToThread(self.ap_settings.fernet.encrypt, ':'.join([
            'm', self.uaid, self.chid]).encode('utf8'))
        d.addCallback(self._route_notification, result, data, ttl)
        return d
Exemple #28
0
    def _uaid_lookup_results(self, result):
        """Process the result of the AWS UAID lookup"""
        # Save the whole record
        router_key = self.router_key = result.get("router_type", "simplepush")
        self._client_info["router_key"] = router_key

        try:
            self.router = self.ap_settings.routers[router_key]
        except KeyError:
            self.log.debug("Invalid router requested",
                           status_code=400,
                           errno=108,
                           **self._client_info)
            return self._write_response(400, 108, message="Invalid router")

        # Only simplepush uses version/data out of body/query, GCM/APNS will
        # use data out of the request body 'WebPush' style.
        use_simplepush = router_key == "simplepush"
        if use_simplepush:
            self.version, data = parse_request_params(self.request)
            self._client_info['message_id'] = self.version
        else:
            data = self.request.body
            if "ttl" not in self.request.headers:
                ttl = None
            # We need crypto headers for messages with payloads.
            req_fields = ["content-encoding", "encryption"]
            if data and not all(
                [x in self.request.headers for x in req_fields]):
                self.log.debug("Client error",
                               status_code=400,
                               errno=101,
                               **self._client_info)
                return self._write_response(400, 101)
            if ("encryption-key" in self.request.headers
                    and "crypto-key" in self.request.headers):
                self.log.debug("Client error",
                               status_code=400,
                               errno=110,
                               **self._client_info)
                return self._write_response(400,
                                            110,
                                            message="Invalid crypto headers")
            self._client_info["message_size"] = len(data) if data else 0

        if "ttl" not in self.request.headers:
            ttl = None
        elif VALID_TTL.match(self.request.headers["ttl"]):
            ttl = int(self.request.headers["ttl"])
            # Cap the TTL to our MAX_TTL
            ttl = min(ttl, MAX_TTL)
        else:
            self.log.debug("Client error",
                           status_code=400,
                           errno=112,
                           **self._client_info)
            return self._write_response(400, 112, message="Invalid TTL header")

        if data and len(data) > self.ap_settings.max_data:
            self.log.debug("Client error",
                           status_code=400,
                           errno=104,
                           **self._client_info)
            return self._write_response(413,
                                        104,
                                        message="Data payload too large")

        if use_simplepush:
            self._route_notification(self.version, result, data)
            return

        # Web Push and bridged messages are encrypted binary blobs. We store
        # and deliver these messages as Base64-encoded strings.
        data = base64url_encode(self.request.body)

        # Generate a message ID, then route the notification.
        d = deferToThread(self.ap_settings.fernet.encrypt,
                          ':'.join(['m', self.uaid, self.chid]).encode('utf8'))
        d.addCallback(self._route_notification, result, data, ttl)
        return d