]> git.ipfire.org Git - pbs.git/blobdiff - src/web/uploads.py
uploads: Rewrite the whole thing
[pbs.git] / src / web / uploads.py
index 69629cb3813bdcdefa1375d9b9e9d9b449a3f260..9172473b2af4e6881d2cbe151be0f1c370e4c4f6 100644 (file)
@@ -23,24 +23,14 @@ import io
 import tornado.web
 
 from . import base
+from .. import uploads
 from .. import users
 
-@tornado.web.stream_request_body
 class APIv1IndexHandler(base.APIMixin, tornado.web.RequestHandler):
        # Allow users to perform uploads
        allow_users = True
 
-       def initialize(self):
-               # Buffer to cache the uploaded content
-               self.buffer = io.BytesIO()
-
-       def data_received(self, data):
-               """
-                       Called when some data is being received
-               """
-               self.buffer.write(data)
-
-       @tornado.web.authenticated
+       @base.negotiate
        def get(self):
                uploads = []
 
@@ -59,10 +49,10 @@ class APIv1IndexHandler(base.APIMixin, tornado.web.RequestHandler):
                        "uploads" : uploads,
                })
 
-       @tornado.web.authenticated
-       async def put(self):
+       @base.negotiate
+       async def post(self):
                """
-                       Called after the entire file has been received
+                       Creates a new upload and returns its UUID
                """
                # Fetch the filename
                filename = self.get_argument("filename")
@@ -70,14 +60,17 @@ class APIv1IndexHandler(base.APIMixin, tornado.web.RequestHandler):
                # Fetch file size
                size = self.get_argument_int("size")
 
-               # Fetch the digest argument
-               algo, delim, hexdigest = self.get_argument("digest").partition(":")
+               # Fetch the digest algorithm
+               digest_algo = self.get_argument("hexdigest_algo")
 
-               # Convert hexdigest
-               digest = bytes.fromhex(hexdigest)
+               # Fetch the digest
+               hexdigest = self.get_argument("hexdigest")
 
-               # Move to the beginning of the buffer
-               self.buffer.seek(0)
+               # Convert hexdigest
+               try:
+                       digest = bytes.fromhex(hexdigest)
+               except ValueError as e:
+                       raise tornado.web.HTTPError(400, "Invalid hexdigest") from e
 
                # Create a new upload
                with self.db.transaction():
@@ -86,19 +79,20 @@ class APIv1IndexHandler(base.APIMixin, tornado.web.RequestHandler):
                                        filename,
                                        size=size,
                                        owner=self.current_user,
+                                       digest_algo=digest_algo,
+                                       digest=digest,
                                )
 
+                       except uploads.UnsupportedDigestException as e:
+                               raise tornado.web.HTTPError(400,
+                                       "Unsupported digest %s" % digest_algo) from e
+
                        except users.QuotaExceededError as e:
                                raise tornado.web.HTTPError(400,
                                        "Quota exceeded for %s" % self.current_user) from e
 
-                       # Import the payload from the buffer
-                       await upload.copyfrom(self.buffer)
-
-                       # Check the digest
-                       if not await upload.check_digest(algo, digest):
-                               # 422 - Unprocessable Entity
-                               raise tornado.web.HTTPError(422, "Digest did not match")
+                       except ValueError as e:
+                               raise tornado.web.HTTPError(400, "%s" % e) from e
 
                # Send the ID of the upload back to the client
                self.finish({
@@ -107,11 +101,46 @@ class APIv1IndexHandler(base.APIMixin, tornado.web.RequestHandler):
                })
 
 
+@tornado.web.stream_request_body
 class APIv1DetailHandler(base.APIMixin, tornado.web.RequestHandler):
        # Allow users to perform uploads
        allow_users = True
 
-       @tornado.web.authenticated
+       def initialize(self):
+               # Buffer to cache the uploaded content
+               self.buffer = io.BytesIO()
+
+       def data_received(self, data):
+               """
+                       Called when some data is being received
+               """
+               self.buffer.write(data)
+
+       # Yes, this does not require authentication. You have seen this correctly.
+       # This is because of us using SPNEGO which might cause a request being sent
+       # more than once, which therefore means that the payload is being transferred
+       # more than once.
+       # To avoid this, we request the digest when the upload is being created, we
+       # then generate a unique ID which an attacker would have to guess first and
+       # then have to upload a file which's hash collides with the original file.
+       async def put(self, uuid):
+               """
+                       Called to store the received payload
+               """
+               # Fetch the upload
+               upload = self.backend.uploads.get_by_uuid(uuid)
+               if not upload:
+                       raise tornado.web.HTTPError(404, "Could not find upload %s" % uuid)
+
+               # Import the payload from the buffer
+               with self.db.transaction():
+                       try:
+                               await upload.copyfrom(self.buffer)
+
+                       except ValueError as e:
+                               raise tornado.web.HTTPError(400, "%s" % e) from e
+
+       @base.negotiate
        async def delete(self, uuid):
                """
                        Deletes an upload with a certain UUID