Skip to content

Commit 0a0b7e0

Browse files
authored
Don't wrap exception views (#16759)
* Move view deriver lower * Add some failing tests * Don't wrap exception views * Fix unit tests
1 parent dcadf7a commit 0a0b7e0

File tree

4 files changed

+153
-8
lines changed

4 files changed

+153
-8
lines changed

tests/functional/forklift/test_legacy.py

+138
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,141 @@ def test_file_upload(webtest, upload_url, additional_data):
132132
assert len(project.releases) == 1
133133
release = project.releases[0]
134134
assert release.version == "3.0.0"
135+
136+
137+
def test_duplicate_file_upload_error(webtest):
138+
user = UserFactory.create(
139+
with_verified_primary_email=True,
140+
password=( # 'password'
141+
"$argon2id$v=19$m=1024,t=6,p=6$EiLE2Nsbo9S6N+acs/beGw$ccyZDCZstr1/+Y/1s3BVZ"
142+
"HOJaqfBroT0JCieHug281c"
143+
),
144+
)
145+
146+
# Construct the macaroon
147+
dm = MacaroonFactory.create(
148+
user_id=user.id,
149+
caveats=[caveats.RequestUser(user_id=str(user.id))],
150+
)
151+
152+
m = pymacaroons.Macaroon(
153+
location="localhost",
154+
identifier=str(dm.id),
155+
key=dm.key,
156+
version=pymacaroons.MACAROON_V2,
157+
)
158+
for caveat in dm.caveats:
159+
m.add_first_party_caveat(caveats.serialize(caveat))
160+
serialized_macaroon = f"pypi-{m.serialize()}"
161+
162+
credentials = base64.b64encode(f"__token__:{serialized_macaroon}".encode()).decode(
163+
"utf-8"
164+
)
165+
166+
with open("./tests/functional/_fixtures/sampleproject-3.0.0.tar.gz", "rb") as f:
167+
content = f.read()
168+
169+
params = MultiDict(
170+
{
171+
":action": "file_upload",
172+
"protocol_version": "1",
173+
"name": "sampleproject",
174+
"sha256_digest": (
175+
"117ed88e5db073bb92969a7545745fd977ee85b7019706dd256a64058f70963d"
176+
),
177+
"filetype": "sdist",
178+
"metadata_version": "2.1",
179+
"version": "3.0.0",
180+
}
181+
)
182+
183+
webtest.post(
184+
"/legacy/",
185+
headers={"Authorization": f"Basic {credentials}"},
186+
params=params,
187+
upload_files=[("content", "sampleproject-3.0.0.tar.gz", content)],
188+
status=HTTPStatus.OK,
189+
)
190+
191+
assert user.projects
192+
assert len(user.projects) == 1
193+
project = user.projects[0]
194+
assert project.name == "sampleproject"
195+
assert project.releases
196+
assert len(project.releases) == 1
197+
release = project.releases[0]
198+
assert release.version == "3.0.0"
199+
200+
# Add some duplicate keys to ensure that this doesn't result in a error due
201+
# to the duplicate key detector
202+
params.add("project-url", "https://example.com/foo")
203+
params.add("project-url", "https://example.com/bar")
204+
params.add("classifiers", "Programming Language :: Python :: 3.10")
205+
params.add("classifiers", "Programming Language :: Python :: 3.11")
206+
207+
resp = webtest.post(
208+
"/legacy/",
209+
headers={"Authorization": f"Basic {credentials}"},
210+
params=params,
211+
upload_files=[("content", "sampleproject-3.0.1.tar.gz", content)],
212+
status=HTTPStatus.BAD_REQUEST,
213+
)
214+
assert "File already exists" in resp.body.decode()
215+
216+
217+
def test_invalid_classifier_upload_error(webtest):
218+
user = UserFactory.create(
219+
with_verified_primary_email=True,
220+
password=( # 'password'
221+
"$argon2id$v=19$m=1024,t=6,p=6$EiLE2Nsbo9S6N+acs/beGw$ccyZDCZstr1/+Y/1s3BVZ"
222+
"HOJaqfBroT0JCieHug281c"
223+
),
224+
)
225+
226+
# Construct the macaroon
227+
dm = MacaroonFactory.create(
228+
user_id=user.id,
229+
caveats=[caveats.RequestUser(user_id=str(user.id))],
230+
)
231+
232+
m = pymacaroons.Macaroon(
233+
location="localhost",
234+
identifier=str(dm.id),
235+
key=dm.key,
236+
version=pymacaroons.MACAROON_V2,
237+
)
238+
for caveat in dm.caveats:
239+
m.add_first_party_caveat(caveats.serialize(caveat))
240+
serialized_macaroon = f"pypi-{m.serialize()}"
241+
242+
credentials = base64.b64encode(f"__token__:{serialized_macaroon}".encode()).decode(
243+
"utf-8"
244+
)
245+
246+
with open("./tests/functional/_fixtures/sampleproject-3.0.0.tar.gz", "rb") as f:
247+
content = f.read()
248+
249+
params = MultiDict(
250+
{
251+
":action": "file_upload",
252+
"protocol_version": "1",
253+
"name": "sampleproject",
254+
"sha256_digest": (
255+
"117ed88e5db073bb92969a7545745fd977ee85b7019706dd256a64058f70963d"
256+
),
257+
"filetype": "sdist",
258+
"metadata_version": "2.1",
259+
"version": "3.0.0",
260+
}
261+
)
262+
params.add("classifiers", "Programming Language :: Python :: 3.10")
263+
params.add("classifiers", "This :: Is :: Invalid")
264+
265+
resp = webtest.post(
266+
"/legacy/",
267+
headers={"Authorization": f"Basic {credentials}"},
268+
params=params,
269+
upload_files=[("content", "sampleproject-3.0.1.tar.gz", content)],
270+
status=HTTPStatus.BAD_REQUEST,
271+
)
272+
assert "'This :: Is :: Invalid' is not a valid classifier" in resp.body.decode()

tests/functional/test_config.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,6 @@ def test_rejects_duplicate_post_keys(webtest, socket_enabled):
3636
body.add("foo", "bar")
3737
body.add("foo", "baz")
3838

39-
resp = webtest.post("/account/login", params=body, status=HTTPStatus.BAD_REQUEST)
39+
resp = webtest.post("/account/login/", params=body, status=HTTPStatus.BAD_REQUEST)
4040
assert "POST body may not contain duplicate keys" in resp.body.decode()
41-
assert "(URL: 'http://localhost/account/login')" in resp.body.decode()
41+
assert "(URL: 'http://localhost/account/login/')" in resp.body.decode()

tests/unit/test_config.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,9 @@ def __init__(self):
534534
]
535535
assert configurator_obj.add_view_deriver.calls == [
536536
pretend.call(
537-
config.reject_duplicate_post_keys_view, under=config.viewderivers.INGRESS
537+
config.reject_duplicate_post_keys_view,
538+
over="rendered_view",
539+
under="decorated_view",
538540
)
539541
]
540542

warehouse/config.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import platformdirs
2626
import transaction
2727

28-
from pyramid import renderers, viewderivers
28+
from pyramid import renderers
2929
from pyramid.authorization import Allow, Authenticated
3030
from pyramid.config import Configurator as _Configurator
3131
from pyramid.exceptions import HTTPForbidden
@@ -269,7 +269,12 @@ def from_base64_encoded_json(configuration):
269269

270270

271271
def reject_duplicate_post_keys_view(view, info):
272-
if not info.options.get("permit_duplicate_post_keys"):
272+
if info.options.get("permit_duplicate_post_keys") or info.exception_only:
273+
return view
274+
275+
else:
276+
# If this isn't an exception or hasn't been permitted to have duplicate
277+
# POST keys, wrap the view with a check
273278

274279
@functools.wraps(view)
275280
def wrapped(context, request):
@@ -287,8 +292,6 @@ def wrapped(context, request):
287292

288293
return wrapped
289294

290-
return view
291-
292295

293296
reject_duplicate_post_keys_view.options = {"permit_duplicate_post_keys"} # type: ignore
294297

@@ -845,7 +848,9 @@ def configure(settings=None):
845848
)
846849

847850
# Reject requests with duplicate POST keys
848-
config.add_view_deriver(reject_duplicate_post_keys_view, under=viewderivers.INGRESS)
851+
config.add_view_deriver(
852+
reject_duplicate_post_keys_view, over="rendered_view", under="decorated_view"
853+
)
849854

850855
# Enable Warehouse to serve our static files
851856
prevent_http_cache = config.get_settings().get("pyramid.prevent_http_cache", False)

0 commit comments

Comments
 (0)