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)
def test_empty(self): self.assertFalse(crypto.constant_time_compare("abcdefg", "")) self.assertFalse(crypto.constant_time_compare("", "hijklmnop"))
def test_equal(self): self.assertTrue(crypto.constant_time_compare("abcdefg", "abcdefg"))
def test_inequal(self): self.assertFalse(crypto.constant_time_compare("abcdefg", "hijklmnop"))
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)