Repository: voxel51/fiftyone

Issue:
[BUG] CSRF Failed when connecting to CVAT servers
When connecting to some CVAT servers like [cvat.org](https://cvat.org), the login fails with the following error:
```
Exception: 403 error for request <PreparedRequest [POST]> to url https://cvat.org/api/tasks with the reason Forbidden. 
Error content: b'{"detail":"CSRF Failed: Referer checking failed - no Referer."}'
```

This can be circumvented by adding the `Referer` header pointing to the server API login url:
```python
results = dataset.annotate(
    anno_key,
    label_field=label_field,
    headers={"Referer": "https://cvat.org/api/auth/login"},
)
```

The reason for this issue is still unclear to me, but it can be resolved by checking the login attempt for a 403 error and automatically adding the referer header.


Discussion:

Resolving patch:
diff --git a/fiftyone/utils/cvat.py b/fiftyone/utils/cvat.py
--- a/fiftyone/utils/cvat.py
+++ b/fiftyone/utils/cvat.py
@@ -2984,7 +2984,7 @@ class CVATBackendConfig(foua.AnnotationBackendConfig):
             task. Videos are always uploaded one per task
         segment_size (None): maximum number of images per job. Not applicable
             to videos
-        image_quality (75): an int in `[0, 100]` determining the image quality
+        image_quality (75): an int in ``[0, 100]`` determining the image quality
             to upload to CVAT
         use_cache (True): whether to use a cache when uploading data. Using a
             cache reduces task creation time as data will be processed
@@ -3011,7 +3011,7 @@ class CVATBackendConfig(foua.AnnotationBackendConfig):
             for all objects in the annotation run
         issue_tracker (None): URL(s) of an issue tracker to link to the created
             task(s). This argument can be a list of URLs when annotating videos
-            or when using `task_size` and generating multiple tasks
+            or when using ``task_size`` and generating multiple tasks
     """
 
     def __init__(
@@ -3592,6 +3592,12 @@ def _setup(self):
             self._server_version = 1
             self._login(username, password)
 
+        self._add_referer()
+
+    def _add_referer(self):
+        if "Referer" not in self._session.headers:
+            self._session.headers["Referer"] = self.login_url
+
     def close(self):
         """Closes the API session."""
         self._session.close()
@@ -3847,7 +3853,7 @@ def create_task(
             schema (None): the label schema to use for the created task
             segment_size (None): maximum number of images to load into a job.
                 Not applicable to videos
-            image_quality (75): an int in `[0, 100]` determining the image
+            image_quality (75): an int in ``[0, 100]`` determining the image
                 quality to upload to CVAT
             task_assignee (None): the username to assign the created task(s)
             project_id (None): the ID of a project to which upload the task
@@ -3979,7 +3985,7 @@ def upload_data(
         Args:
             task_id: the task ID
             paths: a list of media paths to upload
-            image_quality (75): an int in `[0, 100]` determining the image
+            image_quality (75): an int in ``[0, 100]`` determining the image
                 quality to upload to CVAT
             use_cache (True): whether to use a cache when uploading data. Using
                 a cache reduces task creation time as data will be processed


Test patch: