Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
bdf20aa
feat(s3): support SigV4 in addon
tishin-endou May 27, 2026
e46e7ba
fix(s3): use regional endpoints and tolerate missing id separator
tishin-endou Jun 9, 2026
8aac881
Merge pull request #3 from anqiuy/feature/s3-addon-migration
tishin-endou Jun 10, 2026
c5dfc7e
test(s3): remove 54 skips, rewrite tests for SigV4 provider (Step 7)
tishin-endou Jun 19, 2026
e25c95e
fix(s3): fix 9 failing tests in test_provider.py (Step 8)
tishin-endou Jun 19, 2026
d3ff252
test(s3): rewrite test_intra_copy for aiobotocore copy_object (remove…
tishin-endou Jun 20, 2026
d172de9
fix(s3): use MockCoroutine+async ctx for test_intra_copy (Python 3.6 …
tishin-endou Jun 20, 2026
5188e4c
fix(s3): test_intra_copy: mock exists=True to match original HEAD-200…
tishin-endou Jun 20, 2026
446fdc1
fix(s3): include bucket when presigning revisions requests
anqiuy Jun 25, 2026
d36a23c
Merge upstream/develop (004f407f) into feature/s3-sigv4
tishin-endou Sep 22, 2026
4f12dca
test(s3): restore develop's can_intra_* regression guards dropped by …
tishin-endou Sep 22, 2026
c8962f2
chore(s3): remove all commented-out legacy implementations (H-1, H-2)
tishin-endou Sep 22, 2026
1790512
test(s3): reproduce U-1, get_object_versions pages the wrong API cont…
tishin-endou Sep 23, 2026
a261d35
fix(s3): page ListObjectVersions with KeyMarker, collect DeleteMarker…
tishin-endou Sep 23, 2026
460324c
test(s3): reproduce the missing version purge on file delete (V-1..V-5)
tishin-endou Sep 23, 2026
5090c22
fix(s3): purge every version when deleting a file (V-1..V-5)
tishin-endou Sep 23, 2026
4d3f628
test(s3): drive folder delete over the real path instead of a stub (T…
tishin-endou Sep 23, 2026
046110a
test(s3): pin the intra_copy failure contract and its size guard (I-2…
tishin-endou Sep 23, 2026
cdbb450
fix(s3): report intra_copy failures with S3's status and code only (I-2)
tishin-endou Sep 23, 2026
fcbf249
test(s3): pin folder listing paging and its full-listing callers (P-1…
tishin-endou Sep 24, 2026
71fd4ee
feat(s3): return a folder listing one page at a time when asked (P-1.…
tishin-endou Sep 24, 2026
e8b39a9
test(s3): pin folder delete to purging every version and delete marke…
tishin-endou Sep 24, 2026
3ee62df
feat(s3): purge every version of a folder's contents on delete (V-6)
tishin-endou Sep 24, 2026
dda4ed4
test(s3): restore the folder_precheck=False create_folder case (T-2)
tishin-endou Sep 24, 2026
125c797
test(s3): pin error reporting, response parsing and commit handling (…
tishin-endou Sep 24, 2026
de7ea41
fix(s3): report failures without leaking credentials, fail closed on …
tishin-endou Sep 24, 2026
50d2d19
test(s3): pin the signing endpoint and the folder id shapes (IF-1, IF-3)
tishin-endou Sep 24, 2026
8051819
test(s3): pin the intra-copy size limit boundary (T-3 M-3)
tishin-endou Sep 24, 2026
f19e460
test(s3): pin that the multipart commit is sent exactly once (K-5, red)
tishin-endou Sep 24, 2026
20cf30a
fix(s3): send the multipart commit exactly once (K-5, green)
tishin-endou Sep 24, 2026
213eb01
test(s3): pin the three-valued commit outcome (K-4, red)
tishin-endou Sep 24, 2026
901c13e
feat(s3): tell the user when a failed commit's outcome is unknown (K-…
tishin-endou Sep 24, 2026
ca65500
build: pin botocore to the version aiobotocore actually supports (R-3)
tishin-endou Sep 24, 2026
4717ee5
test: run the folder paging tests through the real presigner (red, CX…
tishin-endou Sep 25, 2026
26d1200
fix: send MaxKeys as an int so a folder page reaches S3 (green, CX1-1)
tishin-endou Sep 25, 2026
ebd304e
test: read the chunked-upload query off a real socket (red, CX1-2/R-6…
tishin-endou Sep 25, 2026
6a47f30
fix: stop re-sending the presigned parameters on the wire (green, CX1…
tishin-endou Sep 25, 2026
89b62ad
test: assert listings are decoded by the response's EncodingType (red…
tishin-endou Sep 25, 2026
9249cc6
fix: decode listings by what the response declares, not by guesswork …
tishin-endou Sep 25, 2026
e5760dd
test: require the commit response to report success (red, CX1-4/K-3)
tishin-endou Sep 25, 2026
9fbed44
fix: accept a commit only when the body reports success (green, CX1-4…
tishin-endou Sep 25, 2026
40b3538
test: keep the commit verdict when the abort raises (red, CX1-5/K-1/K-4)
tishin-endou Sep 25, 2026
f480090
fix: report an abort that raised rather than letting it replace the v…
tishin-endou Sep 25, 2026
0cbaa9a
test: forbid the original exception on the chain after conversion (re…
tishin-endou Sep 25, 2026
3b924a9
fix: suppress the exception chain at the conversion points (green, CX…
tishin-endou Sep 25, 2026
83cc021
test(s3): red for the ROUND1 minor group (CX1-8 / CX1-10 / CX1-13 / C…
tishin-endou Sep 25, 2026
1329f0e
fix(s3): close the ROUND1 minor group (CX1-8 / CX1-10 / CX1-13 / CL M…
tishin-endou Sep 25, 2026
d61f732
test(s3): red for G-10 accept_url 直接ダウンロード (決定-19)
tishin-endou Sep 25, 2026
6bbb2fb
fix(s3): G-10 accept_url の直接ダウンロードを復元 (決定-19)
tishin-endou Sep 25, 2026
6015b7a
test(s3): red for 決定-20 intra_copy の資格情報 (CX1-7)
tishin-endou Sep 25, 2026
80f08ed
fix(s3): intra_copy を宛先の資格情報・リージョンで署名する (決定-20 / CX1-7)
tishin-endou Sep 25, 2026
842c88d
test(core,s3): red for 決定-21 dehydrate/rehydrate の撤去 (CL M-1)
tishin-endou Sep 25, 2026
c8ff949
refactor(core,s3): dehydrate/rehydrate を撤去 (決定-21 / CL M-1)
tishin-endou Sep 25, 2026
d44373d
test(s3): T-1 掃除(1) delete 系 17 件を実 presigner に戻す (CX1-11)
tishin-endou Sep 25, 2026
9ad0918
test(s3): T-1 掃除(2) commit 系 4 件の presigner 差し替えを撤去 (CX1-11)
tishin-endou Sep 25, 2026
d284f69
test(s3): run the real presigner everywhere (T-1 / CX1-11, sweep part 3)
tishin-endou Sep 25, 2026
f1c8260
test(s3): build a real client in test_intra_copy too (T-1 / CX1-11, s…
tishin-endou Sep 25, 2026
e155015
fix(s3): resume ListObjectVersions with the version id verbatim (CX2-…
tishin-endou Sep 25, 2026
098556d
test(s3): cover the three cells ROUND2 found unguarded (CX2-2)
tishin-endou Sep 25, 2026
a8c53bc
test(s3): assert deletes and copies on the wire, not on the client (C…
tishin-endou Sep 25, 2026
cf2f87a
test(s3): count the chunked-upload query parameters case-folded (CL 所…
tishin-endou Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
aiocontextvars==0.2.2 # recommended for sentry-sdk
aiohttp==3.6.2
git+https://github.com/felliott/boto.git@feature/gen-url-query-params-6#egg=boto
boto3==1.16.63
boto3==1.16.52
aiobotocore==1.2.2
# aiobotocore 1.2.2 requires botocore<1.19.53,>=1.19.52, but neither it nor boto3 1.16.52
# pins the patch level tightly enough to keep pip inside that window: resolving this file
# without the line below installs botocore 1.19.63 and `pip check` reports the conflict.
# aiobotocore reaches into botocore internals, so the window is not advisory.
botocore==1.19.52
moto==1.3.16
aws-sam-translator==1.42.0
celery==3.1.17
Expand Down
28 changes: 28 additions & 0 deletions tests/core/test_metadata.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import hashlib

import pytest

from tests import utils
from waterbutler.core import metadata


class TestBaseMetadata:
Expand Down Expand Up @@ -114,3 +117,28 @@ def test_file_revision_json_api_serialize(self):
'modified_utc': 'never',
'versionIdentifier': 'versions',
}


class TestNoPayloadDrivenConstruction:
"""CL M-1 / 決定-21: core carries no way to build a metadata class named by a payload.

The ``dehydrate``/``rehydrate`` pair came from COS's Celery serialization work (ENG-7534)
and has nothing to do with SigV4. ``rehydrate`` read a dotted name out of the payload and
turned it into a class -- ``importlib.import_module(...)`` then ``getattr`` -- so anything
that ever reached it with attacker-shaped input would be choosing which class WaterButler
instantiates and what it is called with. Nothing in GRDM calls either method, so the
gadget sat there earning nothing.

``core`` is inherited by every provider, so this is also the one place where the S3 branch
was widening its blast radius beyond S3. These assertions are what keeps a later merge
from restoring the pair without the decision being revisited (台帳 U-11).
"""

@pytest.mark.parametrize('name', ['dehydrate', '_dehydrate', 'rehydrate', '_rehydrate'])
def test_base_metadata_has_no_hydration_hooks(self, name):
assert not hasattr(metadata.BaseMetadata, name)

def test_core_metadata_does_not_import_importlib(self):
"""The import is the gadget's only ingredient; its absence is what makes the removal
real rather than a rename."""
assert not hasattr(metadata, 'importlib')
33 changes: 17 additions & 16 deletions tests/providers/s3/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ def credentials():
@pytest.fixture
def settings():
return {
'bucket': 'that kerning',
'id': 'that-kerning:/my-subfolder/',
'bucket': 'that-kerning',
'encrypt_uploads': False
}

Expand All @@ -42,49 +43,49 @@ def file_content():

@pytest.fixture
def folder_metadata():
with open(os.path.join(os.path.dirname(__file__), 'fixtures/folder_metadata.xml'), 'r') as fp:
with open(os.path.join(os.path.dirname(__file__), 'fixtures/folder_metadata.xml')) as fp:
return fp.read()


@pytest.fixture
def folder_single_item_metadata():
with open(os.path.join(os.path.dirname(__file__),
'fixtures/folder_single_item_metadata.xml'), 'r') as fp:
'fixtures/folder_single_item_metadata.xml')) as fp:
return fp.read()


@pytest.fixture
def folder_item_metadata():
with open(os.path.join(os.path.dirname(__file__),
'fixtures/folder_item_metadata.xml'), 'r') as fp:
'fixtures/folder_item_metadata.xml')) as fp:
return fp.read()


@pytest.fixture
def folder_and_contents():
with open(os.path.join(os.path.dirname(__file__),
'fixtures/folder_and_contents.xml'), 'r') as fp:
'fixtures/folder_and_contents.xml')) as fp:
return fp.read()


@pytest.fixture
def version_metadata():
with open(os.path.join(os.path.dirname(__file__),
'fixtures/version_metadata.xml'), 'r') as fp:
'fixtures/version_metadata.xml')) as fp:
return fp.read()


@pytest.fixture
def single_version_metadata():
with open(os.path.join(os.path.dirname(__file__),
'fixtures/single_version_metadata.xml'), 'r') as fp:
'fixtures/single_version_metadata.xml')) as fp:
return fp.read()


@pytest.fixture
def folder_empty_metadata():
with open(os.path.join(os.path.dirname(__file__),
'fixtures/folder_empty_metadata.xml'), 'r') as fp:
'fixtures/folder_empty_metadata.xml')) as fp:
return fp.read()


Expand All @@ -94,7 +95,7 @@ def file_header_metadata():
'Content-Length': '9001',
'Last-Modified': 'SomeTime',
'Content-Type': 'binary/octet-stream',
'Etag': '"fba9dede5f27731c9771645a39863328"',
'Etag': 'fba9dede5f27731c9771645a39863328',
'x-amz-server-side-encryption': 'AES256'
}

Expand Down Expand Up @@ -154,47 +155,47 @@ def revision_metadata_object():
@pytest.fixture
def create_session_resp():
file_path = 'fixtures/chunked_uploads/create_session_resp.xml'
with open(os.path.join(os.path.dirname(__file__), file_path), 'r') as fp:
with open(os.path.join(os.path.dirname(__file__), file_path)) as fp:
return fp.read()


@pytest.fixture
def generic_http_404_resp():
file_path = 'fixtures/chunked_uploads/generic_http_404_resp.xml'
with open(os.path.join(os.path.dirname(__file__), file_path), 'r') as fp:
with open(os.path.join(os.path.dirname(__file__), file_path)) as fp:
return fp.read()


@pytest.fixture
def generic_http_403_resp():
file_path = 'fixtures/chunked_uploads/generic_http_403_resp.xml'
with open(os.path.join(os.path.dirname(__file__), file_path), 'r') as fp:
with open(os.path.join(os.path.dirname(__file__), file_path)) as fp:
return fp.read()


@pytest.fixture
def list_parts_resp_empty():
file_path = 'fixtures/chunked_uploads/list_parts_resp_empty.xml'
with open(os.path.join(os.path.dirname(__file__), file_path), 'r') as fp:
with open(os.path.join(os.path.dirname(__file__), file_path)) as fp:
return fp.read()


@pytest.fixture
def list_parts_resp_not_empty():
file_path = 'fixtures/chunked_uploads/list_parts_resp_not_empty.xml'
with open(os.path.join(os.path.dirname(__file__), file_path), 'r') as fp:
with open(os.path.join(os.path.dirname(__file__), file_path)) as fp:
return fp.read()


@pytest.fixture
def complete_upload_resp():
file_path = 'fixtures/chunked_uploads/complete_upload_resp.xml'
with open(os.path.join(os.path.dirname(__file__), file_path), 'r') as fp:
with open(os.path.join(os.path.dirname(__file__), file_path)) as fp:
return fp.read()


@pytest.fixture
def upload_parts_headers_list():
file_path = 'fixtures/chunked_uploads/upload_parts_headers_list.json'
with open(os.path.join(os.path.dirname(__file__), file_path), 'r') as fp:
with open(os.path.join(os.path.dirname(__file__), file_path)) as fp:
return fp.read()
15 changes: 15 additions & 0 deletions tests/providers/s3/test_metadata.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest

from waterbutler.providers.s3.metadata import S3Metadata, S3FileMetadataHeaders
from tests.providers.s3.fixtures import (
file_metadata_headers_object,
file_header_metadata,
Expand Down Expand Up @@ -136,3 +137,17 @@ def test_revisions_metadata_not_lastest(self, revision_metadata_object):

assert revision_metadata_object.version == '3/L4kqtJl40Nr8X8gdRQBpUMLUo'


class TestNoHydrationHooks:
"""CL M-1 / 決定-21: the S3 half of the removed Celery serialization hooks is gone too.

``S3FileMetadataHeaders`` overrode ``_dehydrate``/``_rehydrate`` to carry ``_path`` through
the payload. With the base pair removed those overrides call a ``super()`` that no longer
exists, so leaving them behind would be a method that raises the moment anything reaches
it. See ``tests.core.test_metadata.TestNoPayloadDrivenConstruction`` for why the pair went.
"""

@pytest.mark.parametrize('name', ['dehydrate', '_dehydrate', 'rehydrate', '_rehydrate'])
def test_s3_metadata_has_no_hydration_hooks(self, name):
assert not hasattr(S3Metadata, name)
assert not hasattr(S3FileMetadataHeaders, name)
Loading
Loading