prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Viewing of "file"-kind of CIV in archive items leads to 403
  I created an archive item and added a file type CIV but when trying to view the file it leads to a permission denied error. It seems that the permission check when serving a CIV file is missing a check for archive item viewing. It only checks for algorithm jobs and evaluations:
  
  https://github.com/comic/grand-challenge.org/blob/9322d09c0859998a77accb5c13d6db675504a9c1/app/grandchallenge/serving/views.py#L94-L117
  
  Permissions for archives are only done on archive level (vs. archive item level) so we need to add a check here to see if the CIV belongs to an archive item and if the user has the `archives.view_archive` permission for that archive.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `app/grandchallenge/serving/views.py`
  Content:
  ```
  1 import posixpath
  2 
  3 from django.conf import settings
  4 from django.core.exceptions import MultipleObjectsReturned, PermissionDenied
  5 from django.db.models import F, Q
  6 from django.http import Http404, HttpResponseRedirect
  7 from django.utils._os import safe_join
  8 from guardian.shortcuts import get_objects_for_user
  9 from knox.auth import TokenAuthentication
  10 from rest_framework.exceptions import AuthenticationFailed
  11 
  12 from grandchallenge.cases.models import Image
  13 from grandchallenge.challenges.models import ChallengeRequest
  14 from grandchallenge.components.models import ComponentInterfaceValue
  15 from grandchallenge.core.storage import internal_protected_s3_storage
  16 from grandchallenge.evaluation.models import Submission
  17 from grandchallenge.serving.models import Download
  18 
  19 
  20 def protected_storage_redirect(*, name):
  21     # Get the storage with the internal redirect and auth. This will prepend
  22     # settings.AWS_S3_ENDPOINT_URL to the url
  23     if not internal_protected_s3_storage.exists(name=name):
  24         raise Http404("File not found.")
  25 
  26     if settings.PROTECTED_S3_STORAGE_USE_CLOUDFRONT:
  27         response = HttpResponseRedirect(
  28             internal_protected_s3_storage.cloudfront_signed_url(name=name)
  29         )
  30     else:
  31         url = internal_protected_s3_storage.url(name=name)
  32         response = HttpResponseRedirect(url)
  33 
  34     return response
  35 
  36 
  37 def serve_images(request, *, pk, path, pa="", pb=""):
  38     document_root = safe_join(
  39         f"/{settings.IMAGE_FILES_SUBDIRECTORY}", pa, pb, str(pk)
  40     )
  41     path = posixpath.normpath(path).lstrip("/")
  42     name = safe_join(document_root, path)
  43 
  44     try:
  45         image = Image.objects.get(pk=pk)
  46     except Image.DoesNotExist:
  47         raise Http404("Image not found.")
  48 
  49     try:
  50         user, _ = TokenAuthentication().authenticate(request)
  51     except (AuthenticationFailed, TypeError):
  52         user = request.user
  53 
  54     if user.has_perm("view_image", image):
  55         _create_download(creator_id=user.pk, image_id=image.pk)
  56         return protected_storage_redirect(name=name)
  57 
  58     raise PermissionDenied
  59 
  60 
  61 def serve_submissions(request, *, submission_pk, **_):
  62     try:
  63         submission = Submission.objects.get(pk=submission_pk)
  64     except Submission.DoesNotExist:
  65         raise Http404("Submission not found.")
  66 
  67     if request.user.has_perm("view_submission", submission):
  68         _create_download(
  69             creator_id=request.user.pk, submission_id=submission.pk
  70         )
  71         return protected_storage_redirect(
  72             name=submission.predictions_file.name
  73         )
  74 
  75     raise PermissionDenied
  76 
  77 
  78 def serve_component_interface_value(
  79     request, *, component_interface_value_pk, **_
  80 ):
  81     try:
  82         user, _ = TokenAuthentication().authenticate(request)
  83     except (AuthenticationFailed, TypeError):
  84         user = request.user
  85 
  86     try:
  87         # output should only be connected to a single job; throw error if not?
  88         civ = ComponentInterfaceValue.objects.get(
  89             pk=component_interface_value_pk
  90         )
  91     except (MultipleObjectsReturned, ComponentInterfaceValue.DoesNotExist):
  92         raise Http404("No ComponentInterfaceValue found.")
  93 
  94     if (
  95         get_objects_for_user(
  96             user=user, perms="algorithms.view_job", accept_global_perms=False
  97         )
  98         .filter(
  99             Q(outputs__pk=component_interface_value_pk)
  100             | Q(inputs__pk=component_interface_value_pk)
  101         )
  102         .exists()
  103     ):
  104         return protected_storage_redirect(name=civ.file.name)
  105     elif (
  106         get_objects_for_user(
  107             user=user,
  108             perms="evaluation.view_evaluation",
  109             accept_global_perms=False,
  110         )
  111         .filter(
  112             Q(outputs__pk=component_interface_value_pk)
  113             | Q(inputs__pk=component_interface_value_pk)
  114         )
  115         .exists()
  116     ):
  117         return protected_storage_redirect(name=civ.file.name)
  118 
  119     raise PermissionDenied
  120 
  121 
  122 def _create_download(*, creator_id, image_id=None, submission_id=None):
  123     kwargs = {"creator_id": creator_id}
  124 
  125     if image_id is not None:
  126         kwargs["image_id"] = image_id
  127 
  128     if submission_id is not None:
  129         kwargs["submission_id"] = submission_id
  130 
  131     n_updated = Download.objects.filter(**kwargs).update(count=F("count") + 1)
  132 
  133     if n_updated == 0:
  134         Download.objects.create(**kwargs)
  135 
  136 
  137 def serve_structured_challenge_submission_form(
  138     request, *, challenge_request_pk, **_
  139 ):
  140     try:
  141         challenge_request = ChallengeRequest.objects.get(
  142             pk=challenge_request_pk
  143         )
  144     except ChallengeRequest.DoesNotExist:
  145         raise Http404("Challenge request not found.")
  146 
  147     if request.user.has_perm("challenges.view_challengerequest"):
  148         return protected_storage_redirect(
  149             name=challenge_request.structured_challenge_submission_form.name
  150         )
  151     else:
  152         raise PermissionDenied
  153
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/app/grandchallenge/serving/views.py b/app/grandchallenge/serving/views.py
  --- a/app/grandchallenge/serving/views.py
  +++ b/app/grandchallenge/serving/views.py
  @@ -115,6 +115,16 @@
           .exists()
       ):
           return protected_storage_redirect(name=civ.file.name)
  +    elif (
  +        get_objects_for_user(
  +            user=user,
  +            perms="archives.view_archive",
  +            accept_global_perms=False,
  +        )
  +        .filter(items__values__pk=component_interface_value_pk)
  +        .exists()
  +    ):
  +        return protected_storage_redirect(name=civ.file.name)
   
       raise PermissionDenied
  
verification_info: {"golden_diff": "diff --git a/app/grandchallenge/serving/views.py b/app/grandchallenge/serving/views.py\n--- a/app/grandchallenge/serving/views.py\n+++ b/app/grandchallenge/serving/views.py\n@@ -115,6 +115,16 @@\n         .exists()\n     ):\n         return protected_storage_redirect(name=civ.file.name)\n+    elif (\n+        get_objects_for_user(\n+            user=user,\n+            perms=\"archives.view_archive\",\n+            accept_global_perms=False,\n+        )\n+        .filter(items__values__pk=component_interface_value_pk)\n+        .exists()\n+    ):\n+        return protected_storage_redirect(name=civ.file.name)\n \n     raise PermissionDenied\n", "issue": "Viewing of \"file\"-kind of CIV in archive items leads to 403\nI created an archive item and added a file type CIV but when trying to view the file it leads to a permission denied error. It seems that the permission check when serving a CIV file is missing a check for archive item viewing. It only checks for algorithm jobs and evaluations:\r\n\r\nhttps://github.com/comic/grand-challenge.org/blob/9322d09c0859998a77accb5c13d6db675504a9c1/app/grandchallenge/serving/views.py#L94-L117\r\n\r\nPermissions for archives are only done on archive level (vs. archive item level) so we need to add a check here to see if the CIV belongs to an archive item and if the user has the `archives.view_archive` permission for that archive.\n", "before_files": [{"content": "import posixpath\n\nfrom django.conf import settings\nfrom django.core.exceptions import MultipleObjectsReturned, PermissionDenied\nfrom django.db.models import F, Q\nfrom django.http import Http404, HttpResponseRedirect\nfrom django.utils._os import safe_join\nfrom guardian.shortcuts import get_objects_for_user\nfrom knox.auth import TokenAuthentication\nfrom rest_framework.exceptions import AuthenticationFailed\n\nfrom grandchallenge.cases.models import Image\nfrom grandchallenge.challenges.models import ChallengeRequest\nfrom grandchallenge.components.models import ComponentInterfaceValue\nfrom grandchallenge.core.storage import internal_protected_s3_storage\nfrom grandchallenge.evaluation.models import Submission\nfrom grandchallenge.serving.models import Download\n\n\ndef protected_storage_redirect(*, name):\n    # Get the storage with the internal redirect and auth. This will prepend\n    # settings.AWS_S3_ENDPOINT_URL to the url\n    if not internal_protected_s3_storage.exists(name=name):\n        raise Http404(\"File not found.\")\n\n    if settings.PROTECTED_S3_STORAGE_USE_CLOUDFRONT:\n        response = HttpResponseRedirect(\n            internal_protected_s3_storage.cloudfront_signed_url(name=name)\n        )\n    else:\n        url = internal_protected_s3_storage.url(name=name)\n        response = HttpResponseRedirect(url)\n\n    return response\n\n\ndef serve_images(request, *, pk, path, pa=\"\", pb=\"\"):\n    document_root = safe_join(\n        f\"/{settings.IMAGE_FILES_SUBDIRECTORY}\", pa, pb, str(pk)\n    )\n    path = posixpath.normpath(path).lstrip(\"/\")\n    name = safe_join(document_root, path)\n\n    try:\n        image = Image.objects.get(pk=pk)\n    except Image.DoesNotExist:\n        raise Http404(\"Image not found.\")\n\n    try:\n        user, _ = TokenAuthentication().authenticate(request)\n    except (AuthenticationFailed, TypeError):\n        user = request.user\n\n    if user.has_perm(\"view_image\", image):\n        _create_download(creator_id=user.pk, image_id=image.pk)\n        return protected_storage_redirect(name=name)\n\n    raise PermissionDenied\n\n\ndef serve_submissions(request, *, submission_pk, **_):\n    try:\n        submission = Submission.objects.get(pk=submission_pk)\n    except Submission.DoesNotExist:\n        raise Http404(\"Submission not found.\")\n\n    if request.user.has_perm(\"view_submission\", submission):\n        _create_download(\n            creator_id=request.user.pk, submission_id=submission.pk\n        )\n        return protected_storage_redirect(\n            name=submission.predictions_file.name\n        )\n\n    raise PermissionDenied\n\n\ndef serve_component_interface_value(\n    request, *, component_interface_value_pk, **_\n):\n    try:\n        user, _ = TokenAuthentication().authenticate(request)\n    except (AuthenticationFailed, TypeError):\n        user = request.user\n\n    try:\n        # output should only be connected to a single job; throw error if not?\n        civ = ComponentInterfaceValue.objects.get(\n            pk=component_interface_value_pk\n        )\n    except (MultipleObjectsReturned, ComponentInterfaceValue.DoesNotExist):\n        raise Http404(\"No ComponentInterfaceValue found.\")\n\n    if (\n        get_objects_for_user(\n            user=user, perms=\"algorithms.view_job\", accept_global_perms=False\n        )\n        .filter(\n            Q(outputs__pk=component_interface_value_pk)\n            | Q(inputs__pk=component_interface_value_pk)\n        )\n        .exists()\n    ):\n        return protected_storage_redirect(name=civ.file.name)\n    elif (\n        get_objects_for_user(\n            user=user,\n            perms=\"evaluation.view_evaluation\",\n            accept_global_perms=False,\n        )\n        .filter(\n            Q(outputs__pk=component_interface_value_pk)\n            | Q(inputs__pk=component_interface_value_pk)\n        )\n        .exists()\n    ):\n        return protected_storage_redirect(name=civ.file.name)\n\n    raise PermissionDenied\n\n\ndef _create_download(*, creator_id, image_id=None, submission_id=None):\n    kwargs = {\"creator_id\": creator_id}\n\n    if image_id is not None:\n        kwargs[\"image_id\"] = image_id\n\n    if submission_id is not None:\n        kwargs[\"submission_id\"] = submission_id\n\n    n_updated = Download.objects.filter(**kwargs).update(count=F(\"count\") + 1)\n\n    if n_updated == 0:\n        Download.objects.create(**kwargs)\n\n\ndef serve_structured_challenge_submission_form(\n    request, *, challenge_request_pk, **_\n):\n    try:\n        challenge_request = ChallengeRequest.objects.get(\n            pk=challenge_request_pk\n        )\n    except ChallengeRequest.DoesNotExist:\n        raise Http404(\"Challenge request not found.\")\n\n    if request.user.has_perm(\"challenges.view_challengerequest\"):\n        return protected_storage_redirect(\n            name=challenge_request.structured_challenge_submission_form.name\n        )\n    else:\n        raise PermissionDenied\n", "path": "app/grandchallenge/serving/views.py"}], "after_files": [{"content": "import posixpath\n\nfrom django.conf import settings\nfrom django.core.exceptions import MultipleObjectsReturned, PermissionDenied\nfrom django.db.models import F, Q\nfrom django.http import Http404, HttpResponseRedirect\nfrom django.utils._os import safe_join\nfrom guardian.shortcuts import get_objects_for_user\nfrom knox.auth import TokenAuthentication\nfrom rest_framework.exceptions import AuthenticationFailed\n\nfrom grandchallenge.cases.models import Image\nfrom grandchallenge.challenges.models import ChallengeRequest\nfrom grandchallenge.components.models import ComponentInterfaceValue\nfrom grandchallenge.core.storage import internal_protected_s3_storage\nfrom grandchallenge.evaluation.models import Submission\nfrom grandchallenge.serving.models import Download\n\n\ndef protected_storage_redirect(*, name):\n    # Get the storage with the internal redirect and auth. This will prepend\n    # settings.AWS_S3_ENDPOINT_URL to the url\n    if not internal_protected_s3_storage.exists(name=name):\n        raise Http404(\"File not found.\")\n\n    if settings.PROTECTED_S3_STORAGE_USE_CLOUDFRONT:\n        response = HttpResponseRedirect(\n            internal_protected_s3_storage.cloudfront_signed_url(name=name)\n        )\n    else:\n        url = internal_protected_s3_storage.url(name=name)\n        response = HttpResponseRedirect(url)\n\n    return response\n\n\ndef serve_images(request, *, pk, path, pa=\"\", pb=\"\"):\n    document_root = safe_join(\n        f\"/{settings.IMAGE_FILES_SUBDIRECTORY}\", pa, pb, str(pk)\n    )\n    path = posixpath.normpath(path).lstrip(\"/\")\n    name = safe_join(document_root, path)\n\n    try:\n        image = Image.objects.get(pk=pk)\n    except Image.DoesNotExist:\n        raise Http404(\"Image not found.\")\n\n    try:\n        user, _ = TokenAuthentication().authenticate(request)\n    except (AuthenticationFailed, TypeError):\n        user = request.user\n\n    if user.has_perm(\"view_image\", image):\n        _create_download(creator_id=user.pk, image_id=image.pk)\n        return protected_storage_redirect(name=name)\n\n    raise PermissionDenied\n\n\ndef serve_submissions(request, *, submission_pk, **_):\n    try:\n        submission = Submission.objects.get(pk=submission_pk)\n    except Submission.DoesNotExist:\n        raise Http404(\"Submission not found.\")\n\n    if request.user.has_perm(\"view_submission\", submission):\n        _create_download(\n            creator_id=request.user.pk, submission_id=submission.pk\n        )\n        return protected_storage_redirect(\n            name=submission.predictions_file.name\n        )\n\n    raise PermissionDenied\n\n\ndef serve_component_interface_value(\n    request, *, component_interface_value_pk, **_\n):\n    try:\n        user, _ = TokenAuthentication().authenticate(request)\n    except (AuthenticationFailed, TypeError):\n        user = request.user\n\n    try:\n        # output should only be connected to a single job; throw error if not?\n        civ = ComponentInterfaceValue.objects.get(\n            pk=component_interface_value_pk\n        )\n    except (MultipleObjectsReturned, ComponentInterfaceValue.DoesNotExist):\n        raise Http404(\"No ComponentInterfaceValue found.\")\n\n    if (\n        get_objects_for_user(\n            user=user, perms=\"algorithms.view_job\", accept_global_perms=False\n        )\n        .filter(\n            Q(outputs__pk=component_interface_value_pk)\n            | Q(inputs__pk=component_interface_value_pk)\n        )\n        .exists()\n    ):\n        return protected_storage_redirect(name=civ.file.name)\n    elif (\n        get_objects_for_user(\n            user=user,\n            perms=\"evaluation.view_evaluation\",\n            accept_global_perms=False,\n        )\n        .filter(\n            Q(outputs__pk=component_interface_value_pk)\n            | Q(inputs__pk=component_interface_value_pk)\n        )\n        .exists()\n    ):\n        return protected_storage_redirect(name=civ.file.name)\n    elif (\n        get_objects_for_user(\n            user=user,\n            perms=\"archives.view_archive\",\n            accept_global_perms=False,\n        )\n        .filter(items__values__pk=component_interface_value_pk)\n        .exists()\n    ):\n        return protected_storage_redirect(name=civ.file.name)\n\n    raise PermissionDenied\n\n\ndef _create_download(*, creator_id, image_id=None, submission_id=None):\n    kwargs = {\"creator_id\": creator_id}\n\n    if image_id is not None:\n        kwargs[\"image_id\"] = image_id\n\n    if submission_id is not None:\n        kwargs[\"submission_id\"] = submission_id\n\n    n_updated = Download.objects.filter(**kwargs).update(count=F(\"count\") + 1)\n\n    if n_updated == 0:\n        Download.objects.create(**kwargs)\n\n\ndef serve_structured_challenge_submission_form(\n    request, *, challenge_request_pk, **_\n):\n    try:\n        challenge_request = ChallengeRequest.objects.get(\n            pk=challenge_request_pk\n        )\n    except ChallengeRequest.DoesNotExist:\n        raise Http404(\"Challenge request not found.\")\n\n    if request.user.has_perm(\"challenges.view_challengerequest\"):\n        return protected_storage_redirect(\n            name=challenge_request.structured_challenge_submission_form.name\n        )\n    else:\n        raise PermissionDenied\n", "path": "app/grandchallenge/serving/views.py"}]}