Changeset View
Changeset View
Standalone View
Standalone View
swh/deposit/api/common.py
# Copyright (C) 2017-2020 The Software Heritage developers | # Copyright (C) 2017-2020 The Software Heritage developers | ||||
# See the AUTHORS file at the top-level directory of this distribution | # See the AUTHORS file at the top-level directory of this distribution | ||||
# License: GNU General Public License version 3, or any later version | # License: GNU General Public License version 3, or any later version | ||||
# See top-level LICENSE file for more information | # See top-level LICENSE file for more information | ||||
from abc import ABCMeta, abstractmethod | from abc import ABCMeta, abstractmethod | ||||
import datetime | import datetime | ||||
import hashlib | import hashlib | ||||
import json | import json | ||||
from typing import Any, Dict, Optional, Sequence, Tuple, Type, Union | from typing import Any, Dict, Optional, Sequence, Tuple, Type, Union | ||||
import attr | import attr | ||||
from django.core.files.uploadedfile import InMemoryUploadedFile | |||||
from django.http import FileResponse, HttpResponse | from django.http import FileResponse, HttpResponse | ||||
from django.shortcuts import render | from django.shortcuts import render | ||||
from django.urls import reverse | from django.urls import reverse | ||||
from django.utils import timezone | from django.utils import timezone | ||||
from rest_framework import status | from rest_framework import status | ||||
from rest_framework.authentication import BaseAuthentication, BasicAuthentication | from rest_framework.authentication import BaseAuthentication, BasicAuthentication | ||||
from rest_framework.permissions import BasePermission, IsAuthenticated | from rest_framework.permissions import BasePermission, IsAuthenticated | ||||
from rest_framework.request import Request | from rest_framework.request import Request | ||||
▲ Show 20 Lines • Show All 62 Lines • ▼ Show 20 Lines | class ParsedRequestHeaders: | ||||
content_md5sum = attr.ib(type=Optional[bytes]) | content_md5sum = attr.ib(type=Optional[bytes]) | ||||
packaging = attr.ib(type=Optional[str]) | packaging = attr.ib(type=Optional[str]) | ||||
slug = attr.ib(type=Optional[str]) | slug = attr.ib(type=Optional[str]) | ||||
on_behalf_of = attr.ib(type=Optional[str]) | on_behalf_of = attr.ib(type=Optional[str]) | ||||
metadata_relevant = attr.ib(type=Optional[str]) | metadata_relevant = attr.ib(type=Optional[str]) | ||||
swhid = attr.ib(type=Optional[str]) | swhid = attr.ib(type=Optional[str]) | ||||
def _compute_md5(filehandler: InMemoryUploadedFile) -> bytes: | |||||
h = hashlib.md5() | |||||
for chunk in filehandler: | |||||
h.update(chunk) # type: ignore | |||||
return h.digest() | |||||
class AuthenticatedAPIView(APIView): | class AuthenticatedAPIView(APIView): | ||||
"""Mixin intended as a based API view to enforce the basic | """Mixin intended as a based API view to enforce the basic | ||||
authentication check | authentication check | ||||
""" | """ | ||||
authentication_classes: Sequence[Type[BaseAuthentication]] = (BasicAuthentication,) | authentication_classes: Sequence[Type[BaseAuthentication]] = (BasicAuthentication,) | ||||
permission_classes: Sequence[Type[BasePermission]] = (IsAuthenticated,) | permission_classes: Sequence[Type[BasePermission]] = (IsAuthenticated,) | ||||
▲ Show 20 Lines • Show All 46 Lines • ▼ Show 20 Lines | def _read_headers(self, request: Request) -> ParsedRequestHeaders: | ||||
content_md5sum=content_md5sum, | content_md5sum=content_md5sum, | ||||
packaging=meta.get("HTTP_PACKAGING"), | packaging=meta.get("HTTP_PACKAGING"), | ||||
slug=meta.get("HTTP_SLUG"), | slug=meta.get("HTTP_SLUG"), | ||||
on_behalf_of=meta.get("HTTP_ON_BEHALF_OF"), | on_behalf_of=meta.get("HTTP_ON_BEHALF_OF"), | ||||
metadata_relevant=meta.get("HTTP_METADATA_RELEVANT"), | metadata_relevant=meta.get("HTTP_METADATA_RELEVANT"), | ||||
swhid=meta.get("HTTP_X_CHECK_SWHID"), | swhid=meta.get("HTTP_X_CHECK_SWHID"), | ||||
) | ) | ||||
def _compute_md5(self, filehandler) -> bytes: | |||||
"""Compute uploaded file's md5 sum. | |||||
Args: | |||||
filehandler (InMemoryUploadedFile): the file to compute the md5 | |||||
hash | |||||
Returns: | |||||
the md5 checksum (str) | |||||
""" | |||||
h = hashlib.md5() | |||||
for chunk in filehandler: | |||||
h.update(chunk) | |||||
return h.digest() | |||||
def _deposit_put( | def _deposit_put( | ||||
self, | self, | ||||
request: Request, | request: Request, | ||||
deposit_id: Optional[int] = None, | deposit_id: Optional[int] = None, | ||||
in_progress: bool = False, | in_progress: bool = False, | ||||
external_id: Optional[str] = None, | external_id: Optional[str] = None, | ||||
) -> Deposit: | ) -> Deposit: | ||||
"""Save/Update a deposit in db. | """Save/Update a deposit in db. | ||||
▲ Show 20 Lines • Show All 189 Lines • ▼ Show 20 Lines | ) -> Optional[Dict]: | ||||
length = filehandler.size | length = filehandler.size | ||||
if length != content_length: | if length != content_length: | ||||
return make_error_dict( | return make_error_dict( | ||||
status.HTTP_412_PRECONDITION_FAILED, "Wrong length" | status.HTTP_412_PRECONDITION_FAILED, "Wrong length" | ||||
) | ) | ||||
if md5sum: | if md5sum: | ||||
_md5sum = self._compute_md5(filehandler) | _md5sum = _compute_md5(filehandler) | ||||
if _md5sum != md5sum: | if _md5sum != md5sum: | ||||
return make_error_dict( | return make_error_dict( | ||||
CHECKSUM_MISMATCH, | CHECKSUM_MISMATCH, | ||||
"Wrong md5 hash", | "Wrong md5 hash", | ||||
f"The checksum sent {hashutil.hash_to_hex(md5sum)} and the actual " | f"The checksum sent {hashutil.hash_to_hex(md5sum)} and the actual " | ||||
f"checksum {hashutil.hash_to_hex(_md5sum)} does not match.", | f"checksum {hashutil.hash_to_hex(_md5sum)} does not match.", | ||||
) | ) | ||||
▲ Show 20 Lines • Show All 826 Lines • Show Last 20 Lines |