* Update the s3 post functionality to better support success_action_redirect
- Add the bucket/key values to the redirect url like s3 does, which
supports code that relies on the key value being there on the
redirect.
- Add support for replacing ${filename} in the key value with the actual
filename from the form upload.
See Issue #3667
* Update s3 tests for changed success_action_redirect behavior
- Adds a new test called test_s3_server_post_to_bucket_redirect that
tests both the ${filename} replacement and the key/value addition to the
redirect query args
- Updated the test_creating_presigned_post checks to handle the
key/value additions to the redirect url.
* Fix test updates to work with python2.7
- remove f-string usage
- fix urllib.parse imports to use six
Co-authored-by: Wynn Wilkes <wynn@leading2lean.com>
			
			
This commit is contained in:
		
							parent
							
								
									46679b3b02
								
							
						
					
					
						commit
						891118a7c7
					
				| @ -1,5 +1,6 @@ | |||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
| 
 | 
 | ||||||
|  | import os | ||||||
| import re | import re | ||||||
| import sys | import sys | ||||||
| 
 | 
 | ||||||
| @ -11,7 +12,14 @@ from moto.core.utils import ( | |||||||
|     py2_strip_unicode_keys, |     py2_strip_unicode_keys, | ||||||
|     unix_time_millis, |     unix_time_millis, | ||||||
| ) | ) | ||||||
| from six.moves.urllib.parse import parse_qs, urlparse, unquote, parse_qsl | from six.moves.urllib.parse import ( | ||||||
|  |     parse_qs, | ||||||
|  |     parse_qsl, | ||||||
|  |     urlparse, | ||||||
|  |     unquote, | ||||||
|  |     urlencode, | ||||||
|  |     urlunparse, | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| import xmltodict | import xmltodict | ||||||
| 
 | 
 | ||||||
| @ -859,10 +867,28 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): | |||||||
|         if "file" in form: |         if "file" in form: | ||||||
|             f = form["file"] |             f = form["file"] | ||||||
|         else: |         else: | ||||||
|             f = request.files["file"].stream.read() |             fobj = request.files["file"] | ||||||
|  |             f = fobj.stream.read() | ||||||
|  |             key = key.replace("${filename}", os.path.basename(fobj.filename)) | ||||||
| 
 | 
 | ||||||
|         if "success_action_redirect" in form: |         if "success_action_redirect" in form: | ||||||
|             response_headers["Location"] = form["success_action_redirect"] |             redirect = form["success_action_redirect"] | ||||||
|  |             parts = urlparse(redirect) | ||||||
|  |             queryargs = parse_qs(parts.query) | ||||||
|  |             queryargs["key"] = key | ||||||
|  |             queryargs["bucket"] = bucket_name | ||||||
|  |             redirect_queryargs = urlencode(queryargs, doseq=True) | ||||||
|  |             newparts = ( | ||||||
|  |                 parts.scheme, | ||||||
|  |                 parts.netloc, | ||||||
|  |                 parts.path, | ||||||
|  |                 parts.params, | ||||||
|  |                 redirect_queryargs, | ||||||
|  |                 parts.fragment, | ||||||
|  |             ) | ||||||
|  |             fixed_redirect = urlunparse(newparts) | ||||||
|  | 
 | ||||||
|  |             response_headers["Location"] = fixed_redirect | ||||||
| 
 | 
 | ||||||
|         if "success_action_status" in form: |         if "success_action_status" in form: | ||||||
|             status_code = form["success_action_status"] |             status_code = form["success_action_status"] | ||||||
|  | |||||||
| @ -7,6 +7,7 @@ import os | |||||||
| from boto3 import Session | from boto3 import Session | ||||||
| from six.moves.urllib.request import urlopen | from six.moves.urllib.request import urlopen | ||||||
| from six.moves.urllib.error import HTTPError | from six.moves.urllib.error import HTTPError | ||||||
|  | from six.moves.urllib.parse import urlparse, parse_qs | ||||||
| from functools import wraps | from functools import wraps | ||||||
| from gzip import GzipFile | from gzip import GzipFile | ||||||
| from io import BytesIO | from io import BytesIO | ||||||
| @ -4814,9 +4815,11 @@ def test_creating_presigned_post(): | |||||||
|         {"success_action_redirect": success_url}, |         {"success_action_redirect": success_url}, | ||||||
|     ] |     ] | ||||||
|     conditions.append(["content-length-range", 1, 30]) |     conditions.append(["content-length-range", 1, 30]) | ||||||
|  | 
 | ||||||
|  |     real_key = "{file_uid}.txt".format(file_uid=file_uid) | ||||||
|     data = s3.generate_presigned_post( |     data = s3.generate_presigned_post( | ||||||
|         Bucket=bucket, |         Bucket=bucket, | ||||||
|         Key="{file_uid}.txt".format(file_uid=file_uid), |         Key=real_key, | ||||||
|         Fields={ |         Fields={ | ||||||
|             "content-type": "text/plain", |             "content-type": "text/plain", | ||||||
|             "success_action_redirect": success_url, |             "success_action_redirect": success_url, | ||||||
| @ -4828,14 +4831,15 @@ def test_creating_presigned_post(): | |||||||
|     resp = requests.post( |     resp = requests.post( | ||||||
|         data["url"], data=data["fields"], files={"file": fdata}, allow_redirects=False |         data["url"], data=data["fields"], files={"file": fdata}, allow_redirects=False | ||||||
|     ) |     ) | ||||||
|     assert resp.headers["Location"] == success_url |  | ||||||
|     assert resp.status_code == 303 |     assert resp.status_code == 303 | ||||||
|     assert ( |     redirect = resp.headers["Location"] | ||||||
|         s3.get_object(Bucket=bucket, Key="{file_uid}.txt".format(file_uid=file_uid))[ |     assert redirect.startswith(success_url) | ||||||
|             "Body" |     parts = urlparse(redirect) | ||||||
|         ].read() |     args = parse_qs(parts.query) | ||||||
|         == fdata |     assert args["key"][0] == real_key | ||||||
|     ) |     assert args["bucket"][0] == bucket | ||||||
|  | 
 | ||||||
|  |     assert s3.get_object(Bucket=bucket, Key=real_key)["Body"].read() == fdata | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @mock_s3 | @mock_s3 | ||||||
|  | |||||||
| @ -1,6 +1,8 @@ | |||||||
| # coding=utf-8 | # coding=utf-8 | ||||||
| 
 | 
 | ||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
|  | import io | ||||||
|  | from six.moves.urllib.parse import urlparse, parse_qs | ||||||
| import sure  # noqa | import sure  # noqa | ||||||
| 
 | 
 | ||||||
| from flask.testing import FlaskClient | from flask.testing import FlaskClient | ||||||
| @ -80,6 +82,39 @@ def test_s3_server_post_to_bucket(): | |||||||
|     res.data.should.equal(b"nothing") |     res.data.should.equal(b"nothing") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def test_s3_server_post_to_bucket_redirect(): | ||||||
|  |     test_client = authenticated_client() | ||||||
|  | 
 | ||||||
|  |     res = test_client.put("/", "http://tester.localhost:5000/") | ||||||
|  |     res.status_code.should.equal(200) | ||||||
|  | 
 | ||||||
|  |     redirect_base = "https://redirect.com/success/" | ||||||
|  |     filecontent = "nothing" | ||||||
|  |     filename = "test_filename.txt" | ||||||
|  |     res = test_client.post( | ||||||
|  |         "/", | ||||||
|  |         "https://tester.localhost:5000/", | ||||||
|  |         data={ | ||||||
|  |             "key": "asdf/the-key/${filename}", | ||||||
|  |             "file": (io.BytesIO(filecontent.encode("utf8")), filename), | ||||||
|  |             "success_action_redirect": redirect_base, | ||||||
|  |         }, | ||||||
|  |     ) | ||||||
|  |     real_key = "asdf/the-key/{}".format(filename) | ||||||
|  |     res.status_code.should.equal(303) | ||||||
|  |     redirect = res.headers["location"] | ||||||
|  |     assert redirect.startswith(redirect_base) | ||||||
|  | 
 | ||||||
|  |     parts = urlparse(redirect) | ||||||
|  |     args = parse_qs(parts.query) | ||||||
|  |     assert args["key"][0] == real_key | ||||||
|  |     assert args["bucket"][0] == "tester" | ||||||
|  | 
 | ||||||
|  |     res = test_client.get("/{}".format(real_key), "http://tester.localhost:5000/") | ||||||
|  |     res.status_code.should.equal(200) | ||||||
|  |     res.data.should.equal(filecontent.encode("utf8")) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def test_s3_server_post_without_content_length(): | def test_s3_server_post_without_content_length(): | ||||||
|     test_client = authenticated_client() |     test_client = authenticated_client() | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user