Skip to content

Commit a04387c

Browse files
committed
FIX: Correctly implement of max_size_bytes to avoid signature mismatch
1 parent 62d0ca0 commit a04387c

File tree

9 files changed

+138
-108
lines changed

9 files changed

+138
-108
lines changed

django_gcp/static/django_gcp/cloud_object_widget.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,17 @@ $(document).ready(function($) {
191191
// var field = wrapper.find("input[type=hidden]");
192192
var file = wrapper.find(".gcp-file-input").get(0).files[0];
193193
const signed_ingress_url = wrapper.data("signed-ingress-url")
194-
// const ingress_path = wrapper.data("ingress-path")
194+
const max_size_bytes = wrapper.data("max-size-bytes")
195+
const headers = {}
196+
if (max_size_bytes !== 0) {
197+
headers["X-Goog-Content-Length-Range"] = `0,${max_size_bytes}`
198+
}
195199
$.ajax({
196200
url: signed_ingress_url,
197201
type: "PUT",
198202
data: file,
199203
contentType: "application/octet-stream",
204+
headers: headers,
200205
success: function() {
201206
// field.val(JSON.stringify({_tmp_path: ingress_path, name: file.name, content_type: file.type}));
202207
window.gcp_number_of_files--;

django_gcp/storage/fields.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,11 @@
1212

1313
from .forms import CloudObjectFormField
1414
from .gcloud import GoogleCloudStorage
15-
from .operations import blob_exists, copy_blob, get_signed_upload_url
15+
from .operations import UNLIMITED_MAX_SIZE, blob_exists, copy_blob, get_signed_upload_url
1616
from .widgets import DEFAULT_ACCEPT_MIMETYPE, CloudObjectWidget
1717

1818
logger = logging.getLogger(__name__)
1919

20-
21-
# 32 megabyte default size chosen to be consistent with Cloud Run's
22-
# largest request size
23-
DEFAULT_MAX_SIZE_BYTES = 32 * 1024 * 1024
24-
2520
DEFAULT_OVERWRITE_MODE = "never"
2621

2722
OVERWRITE_MODES = [
@@ -102,8 +97,8 @@ def __init__(
10297
ingress_to="_tmp/",
10398
store_key="media",
10499
accept_mimetype=DEFAULT_ACCEPT_MIMETYPE,
105-
max_size_bytes=DEFAULT_MAX_SIZE_BYTES,
106100
overwrite_mode=DEFAULT_OVERWRITE_MODE,
101+
max_size_bytes=None,
107102
on_change=None,
108103
update_attributes=None,
109104
**kwargs,
@@ -114,6 +109,7 @@ def __init__(
114109
self._on_commit_valid = None
115110
self._versioning_enabled = None
116111
self._temporary_path = None
112+
self._max_size_bytes = max_size_bytes
117113
self._primary_key_set_explicitly = "primary_key" in kwargs
118114
self._choices_set_explicitly = "choices" in kwargs
119115
self.overwrite_mode = overwrite_mode
@@ -123,7 +119,6 @@ def __init__(
123119
self.ingress_path = None
124120
self.store_key = store_key
125121
self.accept_mimetype = accept_mimetype
126-
self.max_size_bytes = max_size_bytes
127122
self.on_change = on_change
128123
kwargs["default"] = kwargs.pop("default", None)
129124
kwargs["help_text"] = kwargs.pop("help_text", "GCP cloud storage object")
@@ -178,6 +173,7 @@ def formfield(self, **kwargs):
178173
widget = CloudObjectWidget(
179174
accept_mimetype=self.accept_mimetype,
180175
signed_ingress_url=self._get_signed_ingress_url(),
176+
max_size_bytes=self.max_size_bytes,
181177
ingress_path=self._get_temporary_path(),
182178
)
183179

@@ -195,6 +191,23 @@ def override_blobfield_value(self):
195191
DEFAULT_OVERRIDE_BLOBFIELD_VALUE,
196192
)
197193

194+
@property
195+
def max_size_bytes(self):
196+
"""Shortcut to determine the max size in bytes allowable as an upload
197+
198+
Defaults to unlimited upload size (which is dangerous, but since you're
199+
signing a URL for the user to upload we assume you've established trust
200+
in the user and know what you're doing!).
201+
"""
202+
if self._max_size_bytes is not None:
203+
return self._max_size_bytes
204+
205+
return getattr(
206+
settings,
207+
"GCP_STORAGE_BLOBFIELD_MAX_SIZE_BYTES",
208+
UNLIMITED_MAX_SIZE,
209+
)
210+
198211
def clean(self, value, model_instance, skip_validation=False):
199212
"""Clean the value to ensure correct state and on_commit hooks.
200213
This method is called during clean_fields on model save.

django_gcp/storage/operations.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
logger = logging.getLogger(__name__)
1313

14+
UNLIMITED_MAX_SIZE = 0
15+
1416

1517
def blob_exists(bucket, blob_name):
1618
"""Quick check that a blob with a given name exists in a bucket"""
@@ -182,7 +184,7 @@ def get_generations(bucket, blob_name):
182184
return list(bucket.client.list_blobs(bucket.name, versions=True, prefix=blob_name))
183185

184186

185-
def get_signed_upload_url(bucket, blob_name, timedelta=None, max_size_bytes=None, **kwargs):
187+
def get_signed_upload_url(bucket, blob_name, timedelta=None, max_size_bytes=UNLIMITED_MAX_SIZE, **kwargs):
186188
"""Get a signed URL for uploading a blob to GCS
187189
188190
:param google.cloud.storage.Bucket bucket: The bucket to which the blob will be uploaded
@@ -195,7 +197,8 @@ def get_signed_upload_url(bucket, blob_name, timedelta=None, max_size_bytes=None
195197
timedelta = datetime.timedelta(minutes=60)
196198

197199
blob = bucket.blob(blob_name)
198-
if max_size_bytes is not None:
200+
201+
if max_size_bytes != 0:
199202
content_length_range = f"0,{max_size_bytes}"
200203
headers = kwargs.pop("headers", {})
201204
headers["X-Goog-Content-Length-Range"] = content_length_range

django_gcp/storage/widgets.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from django.conf import settings
44
from django.forms import Widget
55

6+
from .operations import UNLIMITED_MAX_SIZE
7+
68
DEFAULT_ACCEPT_MIMETYPE = "*/*"
79

810

@@ -28,6 +30,7 @@ def __init__(
2830
self,
2931
*args,
3032
signed_ingress_url=None,
33+
max_size_bytes=UNLIMITED_MAX_SIZE,
3134
ingress_path=None,
3235
accept_mimetype=DEFAULT_ACCEPT_MIMETYPE,
3336
**kwargs,
@@ -37,14 +40,15 @@ def __init__(
3740
self._kwargs = kwargs
3841
self.accept_mimetype = accept_mimetype
3942
self.signed_ingress_url = signed_ingress_url
43+
self.max_size_bytes = max_size_bytes
4044
self.ingress_path = ingress_path
4145

4246
if "unfold" in settings.INSTALLED_APPS:
4347
self.template_name = "django_gcp/contrib/unfold/cloud_object_widget.html"
4448

4549
def get_context(self, name, value, attrs):
4650
context = super().get_context(name, value, attrs)
47-
51+
context["max_size_bytes"] = self.max_size_bytes
4852
context["signed_ingress_url"] = self.signed_ingress_url
4953
context["ingress_path"] = self.ingress_path
5054
context["accept_mimetype"] = self.accept_mimetype

django_gcp/templates/django_gcp/cloud_object_widget.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
</div>
1515
{% endwith %} -->
1616

17-
<div class="gcp-wrapper" data-existing-path="{{ existing_path }}" data-signed-ingress-url="{{ signed_ingress_url }}" data-ingress-path="{{ ingress_path }}">
17+
<div class="gcp-wrapper" data-existing-path="{{ existing_path }}" data-signed-ingress-url="{{ signed_ingress_url }}" data-max-size-bytes="{{ max_size_bytes }}" data-ingress-path="{{ ingress_path }}">
1818
<div class="gcp-field-contents">
1919
<input
2020
type="hidden" name="{{ widget.name }}"

django_gcp/templates/django_gcp/contrib/unfold/cloud_object_widget.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{% load i18n %}
22

3-
<div class="gcp-wrapper" data-existing-path="{{ existing_path }}" data-signed-ingress-url="{{ signed_ingress_url }}" data-ingress-path="{{ ingress_path }}">
3+
<div class="gcp-wrapper" data-existing-path="{{ existing_path }}" data-signed-ingress-url="{{ signed_ingress_url }}" data-max-size-bytes="{{ max_size_bytes }}" data-ingress-path="{{ ingress_path }}">
44
<div class="border flex flex-col overflow-hidden rounded-md shadow-sm text-sm max-w-2xl dark:border-gray-700">
55
<input
66
type="hidden" name="{{ widget.name }}"

0 commit comments

Comments
 (0)