예제 #1
0
    def process_request(self, request):
        """Consume an event batch request and return an appropriate response.

        The API spec:

            * the payload is a JSON list of objects, each object being an event
            * batches are at most 40 KiB in size
            * messages are signed with HMAC SHA-256

        If the payload is valid, the events it contains will be put onto the
        event queue.  If there are issues with the request, error events will be
        put into the error queue instead.

        """

        request.environ["events.start_time"] = datetime.datetime.utcnow()

        try:
            signature_header = request.headers["X-Signature"]
        except KeyError:
            keyname = request.GET.get("key", "")
            mac = request.GET.get("mac", "")
        else:
            keyname, mac = parse_signature(signature_header)

        try:
            key = self.keystore[keyname]
        except KeyError:
            keyname = "UNKNOWN"
            key = "INVALID"

        if request.content_length > MAXIMUM_BATCH_SIZE:
            self._publish_error(request, keyname, "TOO_BIG")
            return HTTPRequestEntityTooLarge()
        body = request.body

        if not request.headers.get("User-Agent"):
            self._publish_error(request, keyname, "NO_USERAGENT")
            return HTTPBadRequest("no user-agent provided")

        # Handle Gzipped Requests
        if request.headers.get('Content-Encoding') == 'gzip':
            f = StringIO(body)
            try:
                body = gzip.GzipFile(fileobj=f).read()
            except IOError:
                return HTTPBadRequest("invalid gzip content")

        expected_mac = hmac.new(key, body, hashlib.sha256).hexdigest()
        _LOG.debug('Received request with key: %r, mac: %r, expected_mac: %r',
                   key, mac, expected_mac)
        if not constant_time_compare(expected_mac, mac or ""):
            self._publish_error(request, keyname, "INVALID_MAC")
            return HTTPForbidden()

        try:
            batch = json.loads(body)
        except ValueError:
            self._publish_error(request, keyname, "INVALID_PAYLOAD")
            return HTTPBadRequest("invalid json")

        if not isinstance(batch, list):
            self._publish_error(request, keyname, "INVALID_PAYLOAD")
            return HTTPBadRequest("json root object must be a list")

        reserialized_items = []
        for item in batch:
            reserialized = wrap_and_serialize_event(request, item)
            if len(reserialized) > MAXIMUM_EVENT_SIZE:
                self._publish_error(request, keyname, "EVENT_TOO_BIG")
                return HTTPRequestEntityTooLarge()
            reserialized_items.append(reserialized)

        for item in reserialized_items:
            self.event_queue.put(item)

        self.metrics_client.counter("collected.http." + keyname).increment(
            len(reserialized_items))

        headers = {}
        origin = request.headers.get("Origin")
        if origin and is_allowed_origin(origin, self.allowed_origins):
            headers.update(_CORS_HEADERS)

        return Response(headers=headers)
예제 #2
0
 def test_empty(self):
     self.assertFalse(crypto.constant_time_compare("abcdefg", ""))
     self.assertFalse(crypto.constant_time_compare("", "hijklmnop"))
예제 #3
0
 def test_equal(self):
     self.assertTrue(crypto.constant_time_compare("abcdefg", "abcdefg"))
예제 #4
0
 def test_inequal(self):
     self.assertFalse(crypto.constant_time_compare("abcdefg", "hijklmnop"))
예제 #5
0
    def process_request(self, request):
        """Consume an event batch request and return an appropriate response.

        The API spec:

            * the payload is a JSON list of objects, each object being an event
            * batches are at most 40 KiB in size
            * messages are signed with HMAC SHA-256

        If the payload is valid, the events it contains will be put onto the
        event queue.  If there are issues with the request, error events will be
        put into the error queue instead.

        """

        request.environ["events.start_time"] = datetime.datetime.utcnow()

        try:
            signature_header = request.headers["X-Signature"]
        except KeyError:
            keyname = request.GET.get("key", "")
            mac = request.GET.get("mac", "")
        else:
            keyname, mac = parse_signature(signature_header)

        try:
            key = self.keystore[keyname]
        except KeyError:
            keyname = "UNKNOWN"
            key = "INVALID"

        if request.content_length > MAXIMUM_BATCH_SIZE:
            self._publish_error(request, keyname, "TOO_BIG")
            return HTTPRequestEntityTooLarge()
        body = request.body

        if not request.headers.get("User-Agent"):
            self._publish_error(request, keyname, "NO_USERAGENT")
            return HTTPBadRequest("no user-agent provided")

        # Handle Gzipped Requests
        if request.headers.get('Content-Encoding') == 'gzip':
            f = StringIO(body)
            try:
                body = gzip.GzipFile(fileobj=f).read()
            except IOError:
                return HTTPBadRequest("invalid gzip content")

        expected_mac = hmac.new(key, body, hashlib.sha256).hexdigest()
        _LOG.debug(
            'Received request with key: %r, mac: %r, expected_mac: %r',
            key, mac, expected_mac)
        if not constant_time_compare(expected_mac, mac or ""):
            self._publish_error(request, keyname, "INVALID_MAC")
            return HTTPForbidden()

        try:
            batch = json.loads(body)
        except ValueError:
            self._publish_error(request, keyname, "INVALID_PAYLOAD")
            return HTTPBadRequest("invalid json")

        if not isinstance(batch, list):
            self._publish_error(request, keyname, "INVALID_PAYLOAD")
            return HTTPBadRequest("json root object must be a list")

        reserialized_items = []
        for item in batch:
            reserialized = wrap_and_serialize_event(request, item)
            if len(reserialized) > MAXIMUM_EVENT_SIZE:
                self._publish_error(request, keyname, "EVENT_TOO_BIG")
                return HTTPRequestEntityTooLarge()
            reserialized_items.append(reserialized)

        for item in reserialized_items:
            self.event_queue.put(item)

        self.metrics_client.counter("collected.http." + keyname).increment(
            len(reserialized_items))

        headers = {}
        origin = request.headers.get("Origin")
        if origin and is_allowed_origin(origin, self.allowed_origins):
            headers.update(_CORS_HEADERS)

        return Response(headers=headers)