Skip to content

Commit 50a58f3

Browse files
woodruffwmikethemandi
authored
Provenance retrieval route (#16778)
Co-authored-by: Mike Fiedler <[email protected]> Co-authored-by: Dustin Ingram <[email protected]>
1 parent 2706bfb commit 50a58f3

20 files changed

+523
-442
lines changed

requirements/main.in

-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ requests
6161
requests-aws4auth
6262
redis>=2.8.0,<6.0.0
6363
rfc3986
64-
rfc8785
6564
sentry-sdk
6665
setuptools
6766
sigstore~=3.3.0

tests/common/db/packaging.py

+9
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
JournalEntry,
2929
ProhibitedProjectName,
3030
Project,
31+
Provenance,
3132
Release,
3233
Role,
3334
RoleInvitation,
@@ -142,6 +143,14 @@ class Meta:
142143
)
143144

144145

146+
class ProvenanceFactory(WarehouseFactory):
147+
class Meta:
148+
model = Provenance
149+
150+
file = factory.SubFactory(FileFactory)
151+
provenance = factory.Faker("json")
152+
153+
145154
class FileEventFactory(WarehouseFactory):
146155
class Meta:
147156
model = File.Event

tests/conftest.py

+10
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
from warehouse.accounts import services as account_services
4646
from warehouse.accounts.interfaces import ITokenService, IUserService
4747
from warehouse.admin.flags import AdminFlag, AdminFlagValue
48+
from warehouse.attestations import services as attestations_services
49+
from warehouse.attestations.interfaces import IIntegrityService
4850
from warehouse.email import services as email_services
4951
from warehouse.email.interfaces import IEmailSender
5052
from warehouse.helpdesk import services as helpdesk_services
@@ -174,6 +176,7 @@ def pyramid_services(
174176
project_service,
175177
github_oidc_service,
176178
activestate_oidc_service,
179+
integrity_service,
177180
macaroon_service,
178181
helpdesk_service,
179182
):
@@ -195,6 +198,7 @@ def pyramid_services(
195198
services.register_service(
196199
activestate_oidc_service, IOIDCPublisherService, None, name="activestate"
197200
)
201+
services.register_service(integrity_service, IIntegrityService, None)
198202
services.register_service(macaroon_service, IMacaroonService, None, name="")
199203
services.register_service(helpdesk_service, IHelpDeskService, None)
200204

@@ -326,6 +330,7 @@ def get_app_config(database, nondefaults=None):
326330
"docs.backend": "warehouse.packaging.services.LocalDocsStorage",
327331
"sponsorlogos.backend": "warehouse.admin.services.LocalSponsorLogoStorage",
328332
"billing.backend": "warehouse.subscriptions.services.MockStripeBillingService",
333+
"integrity.backend": "warehouse.attestations.services.NullIntegrityService",
329334
"billing.api_base": "http://stripe:12111",
330335
"billing.api_version": "2020-08-27",
331336
"mail.backend": "warehouse.email.services.SMTPEmailSender",
@@ -557,6 +562,11 @@ def dummy_attestation():
557562
)
558563

559564

565+
@pytest.fixture
566+
def integrity_service(db_session):
567+
return attestations_services.NullIntegrityService(db_session)
568+
569+
560570
@pytest.fixture
561571
def macaroon_service(db_session):
562572
return macaroon_services.DatabaseMacaroonService(db_session)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"version": 1,
3+
"verification_material": {
4+
"certificate": "MIIC6zCCAnGgAwIBAgIUFgmhIYx8gvBGePCTacG/4kbBdRwwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjQwODI5MTcwOTM5WhcNMjQwODI5MTcxOTM5WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtGrMPml4OtsRJ3Z6qRahs0kHCZxP4n9fvrJE957WVxgAGg4k6a1PbRJY9nT9wKpRrZmKV++AgA9ndhdruXXaAKOCAZAwggGMMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUosNvhYEuTPfgyU/dZfu93lFGRNswHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wQAYDVR0RAQH/BDYwNIEyOTE5NDM2MTU4MjM2LWNvbXB1dGVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20wKQYKKwYBBAGDvzABAQQbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMCsGCisGAQQBg78wAQgEHQwbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMIGKBgorBgEEAdZ5AgQCBHwEegB4AHYA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4AAAGRnx0/aQAABAMARzBFAiBogvcKHIIR9FcX1vQgDhGtAl0XQoMRiEB3OdUWO94P1gIhANdJlyISdtvVrHes25dWKTLepy+IzQmzfQU/S7cxWHmOMAoGCCqGSM49BAMDA2gAMGUCMGe2xTiuenbjdt1d2e4IaCiwRh2G4KAtyujRESSSUbpuGme/o9ouiApeONBv2CvvGAIxAOEkAGFO3aALE3IPNosxqaz9MbqJOdmYhB1Cz1D7xbFc/m243VxJWxaC/uOFEpyiYQ==",
5+
"transparency_entries": [
6+
{
7+
"logIndex": "125970014",
8+
"logId": {
9+
"keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="
10+
},
11+
"kindVersion": {
12+
"kind": "dsse",
13+
"version": "0.0.1"
14+
},
15+
"integratedTime": "1724951379",
16+
"inclusionPromise": {
17+
"signedEntryTimestamp": "MEUCIQCHrKFTeXNY432S0bUSBS69S8d5JnNcDXa41q6OEvxEwgIgaZstc5Jpm0IgwFC7RDTXYEAKk+3aG/MkRkaPdJdyn8U="
18+
},
19+
"inclusionProof": {
20+
"logIndex": "4065752",
21+
"rootHash": "7jVDF3UNUZVEU85ffETQ3WKfXhOoMi4cgytJM250HTk=",
22+
"treeSize": "4065754",
23+
"hashes": [
24+
"NwJgWJoxjearbnEIT9bnWXpzo0LGNrR1cpWId0g66rE=",
25+
"kLjpW3Eh7pQJNOvyntghzF57tcfqk2IzX7cqiBDgGf8=",
26+
"FW8y9LQ1i3q+MnbeGJipKGl4VfX1zRBOD7TmhbEw7uI=",
27+
"mKcbGJDJ/+buNbXy9Eyv94nVoAyUauuIlN3cJg3qSBY=",
28+
"5VytqqAHhfRkRWMrY43UXWCnRBb7JwElMlKpY5JueBc=",
29+
"mZJnD39LTKdis2wUTz1OOMx3r7HwgJh9rnb2VwiPzts=",
30+
"MXZOQFJFiOjREF0xwMOCXu29HwTchjTtl/BeFoI51wY=",
31+
"g8zCkHnLwO3LojK7g5AnqE8ezSNRnCSz9nCL5GD3a8A=",
32+
"RrZsD/RSxNoujlvq/MsCEvLSkKZfv0jmQM9Kp7qbJec=",
33+
"QxmVWsbTp4cClxuAkuT51UH2EY7peHMVGKq7+b+cGwQ=",
34+
"Q2LAtNzOUh+3PfwfMyNxYb06fTQmF3VeTT6Fr6Upvfc=",
35+
"ftwAu6v62WFDoDmcZ1JKfrRPrvuiIw5v3BvRsgQj7N8="
36+
],
37+
"checkpoint": {
38+
"envelope": "rekor.sigstore.dev - 1193050959916656506\n4065754\n7jVDF3UNUZVEU85ffETQ3WKfXhOoMi4cgytJM250HTk=\n\n— rekor.sigstore.dev wNI9ajBGAiEAhMomhZHOTNB5CVPO98CMXCv01ZlIF+C+CgzraAB01r8CIQCEuXbv6aqguUpB/ig5eXRIbarvxLXkg3nX48DzambktQ==\n"
39+
}
40+
},
41+
"canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiOWRiNGJjMzE3MTgyZWI3NzljNDIyY2Q0NGI2ZDdlYTk5ZWM1M2Q3M2JiY2ZjZWVmZTIyNWVlYjQ3NTQyMjc4OCJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjlkYjY0MjlhOTkzZGFiYTI4NzAwODk2ZTY2MzNjNzkxYWE0MDM3ODQ4NjJiYzY2MDBkM2E4NjYwMGQzYjA1NjMifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVVQ0lCaGlOL25NR0w3aHpZQk9QQjlUTGtuaEdTZEtuQ0Q0ekI3TDV5ZXc0QmJ3QWlFQXJzOHl6MCtCT2NnSEtzS0JzTXVOeVlhREdaRTBVV0JuMEdwNVpGMzUvU2M9IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VNMmVrTkRRVzVIWjBGM1NVSkJaMGxWUm1kdGFFbFplRGhuZGtKSFpWQkRWR0ZqUnk4MGEySkNaRkozZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwUmQwOUVTVFZOVkdOM1QxUk5OVmRvWTA1TmFsRjNUMFJKTlUxVVkzaFBWRTAxVjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVjBSM0pOVUcxc05FOTBjMUpLTTFvMmNWSmhhSE13YTBoRFduaFFORzQ1Wm5aeVNrVUtPVFUzVjFaNFowRkhaelJyTm1FeFVHSlNTbGs1YmxRNWQwdHdVbkphYlV0V0t5dEJaMEU1Ym1Sb1pISjFXRmhoUVV0UFEwRmFRWGRuWjBkTlRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVnZjMDUyQ21oWlJYVlVVR1puZVZVdlpGcG1kVGt6YkVaSFVrNXpkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMUZCV1VSV1VqQlNRVkZJTDBKRVdYZE9TVVY1VDFSRk5VNUVUVEpOVkZVMFRXcE5Na3hYVG5aaVdFSXhaRWRXUVZwSFZqSmFWM2gyWTBkV2VRcE1iV1I2V2xoS01tRlhUbXhaVjA1cVlqTldkV1JETldwaU1qQjNTMUZaUzB0M1dVSkNRVWRFZG5wQlFrRlJVV0poU0ZJd1kwaE5Oa3g1T1doWk1rNTJDbVJYTlRCamVUVnVZakk1Ym1KSFZYVlpNamwwVFVOelIwTnBjMGRCVVZGQ1p6YzRkMEZSWjBWSVVYZGlZVWhTTUdOSVRUWk1lVGxvV1RKT2RtUlhOVEFLWTNrMWJtSXlPVzVpUjFWMVdUSTVkRTFKUjB0Q1oyOXlRbWRGUlVGa1dqVkJaMUZEUWtoM1JXVm5RalJCU0ZsQk0xUXdkMkZ6WWtoRlZFcHFSMUkwWXdwdFYyTXpRWEZLUzFoeWFtVlFTek12YURSd2VXZERPSEEzYnpSQlFVRkhVbTU0TUM5aFVVRkJRa0ZOUVZKNlFrWkJhVUp2WjNaalMwaEpTVkk1Um1OWUNqRjJVV2RFYUVkMFFXd3dXRkZ2VFZKcFJVSXpUMlJWVjA4NU5GQXhaMGxvUVU1a1NteDVTVk5rZEhaV2NraGxjekkxWkZkTFZFeGxjSGtyU1hwUmJYb0tabEZWTDFNM1kzaFhTRzFQVFVGdlIwTkRjVWRUVFRRNVFrRk5SRUV5WjBGTlIxVkRUVWRsTW5oVWFYVmxibUpxWkhReFpESmxORWxoUTJsM1VtZ3lSd28wUzBGMGVYVnFVa1ZUVTFOVlluQjFSMjFsTDI4NWIzVnBRWEJsVDA1Q2RqSkRkblpIUVVsNFFVOUZhMEZIUms4ellVRk1SVE5KVUU1dmMzaHhZWG81Q2sxaWNVcFBaRzFaYUVJeFEzb3hSRGQ0WWtaakwyMHlORE5XZUVwWGVHRkRMM1ZQUmtWd2VXbFpVVDA5Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn1dfX0="
42+
}
43+
]
44+
},
45+
"envelope": {
46+
"statement": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoic2FtcGxlcHJvamVjdC0zLjAuMC50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiMTE3ZWQ4OGU1ZGIwNzNiYjkyOTY5YTc1NDU3NDVmZDk3N2VlODViNzAxOTcwNmRkMjU2YTY0MDU4ZjcwOTYzZCJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2RvY3MucHlwaS5vcmcvYXR0ZXN0YXRpb25zL3B1Ymxpc2gvdjEiLCJwcmVkaWNhdGUiOm51bGx9",
47+
"signature": "MEUCIBhiN/nMGL7hzYBOPB9TLknhGSdKnCD4zB7L5yew4BbwAiEArs8yz0+BOcgHKsKBsMuNyYaDGZE0UWBn0Gp5ZF35/Sc="
48+
}
49+
}
+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
13+
import json
14+
15+
from http import HTTPStatus
16+
from pathlib import Path
17+
18+
from ...common.db.packaging import (
19+
FileFactory,
20+
ProjectFactory,
21+
ProvenanceFactory,
22+
ReleaseFactory,
23+
)
24+
25+
_HERE = Path(__file__).parent
26+
_ASSETS = _HERE.parent / "_fixtures"
27+
assert _ASSETS.is_dir()
28+
29+
30+
def test_provenance_available(webtest):
31+
with open(
32+
_ASSETS / "sampleproject-3.0.0.tar.gz.publish.attestation",
33+
) as f:
34+
attestation_contents = f.read()
35+
attestation_json = json.loads(attestation_contents)
36+
37+
project = ProjectFactory.create()
38+
release = ReleaseFactory.create(project=project)
39+
file_ = FileFactory.create(release=release, packagetype="sdist")
40+
ProvenanceFactory.create(
41+
file=file_,
42+
provenance={"attestation_bundles": [{"attestations": [attestation_json]}]},
43+
)
44+
45+
response = webtest.get(
46+
f"/integrity/{project.name}/{release.version}/{file_.filename}/provenance",
47+
status=HTTPStatus.OK,
48+
)
49+
assert response.json
50+
assert "attestation_bundles" in response.json
51+
attestation_bundles = response.json["attestation_bundles"]
52+
assert len(attestation_bundles) == 1
53+
attestation_bundle = attestation_bundles[0]
54+
assert "attestations" in attestation_bundle
55+
attestations = attestation_bundle["attestations"]
56+
assert len(attestations) == 1
57+
attestation = attestations[0]
58+
assert attestation == attestation_json

tests/functional/forklift/test_legacy.py

+80-21
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,27 @@
1111
# limitations under the License.
1212

1313
import base64
14+
import json
1415

1516
from http import HTTPStatus
17+
from pathlib import Path
1618

1719
import pymacaroons
1820
import pytest
1921

2022
from webob.multidict import MultiDict
2123

24+
from tests.common.db.oidc import GitHubPublisherFactory
25+
from tests.common.db.packaging import ProjectFactory, RoleFactory
2226
from warehouse.macaroons import caveats
2327

2428
from ...common.db.accounts import UserFactory
2529
from ...common.db.macaroons import MacaroonFactory
2630

31+
_HERE = Path(__file__).parent
32+
_ASSETS = _HERE.parent / "_fixtures"
33+
assert _ASSETS.is_dir()
34+
2735

2836
def test_incorrect_post_redirect(webtest):
2937
"""
@@ -68,13 +76,7 @@ def test_remove_doc_upload(webtest):
6876
],
6977
)
7078
def test_file_upload(webtest, upload_url, additional_data):
71-
user = UserFactory.create(
72-
with_verified_primary_email=True,
73-
password=( # 'password'
74-
"$argon2id$v=19$m=1024,t=6,p=6$EiLE2Nsbo9S6N+acs/beGw$ccyZDCZstr1/+Y/1s3BVZ"
75-
"HOJaqfBroT0JCieHug281c"
76-
),
77-
)
79+
user = UserFactory.create(with_verified_primary_email=True, clear_pwd="password")
7880

7981
# Construct the macaroon
8082
dm = MacaroonFactory.create(
@@ -135,13 +137,7 @@ def test_file_upload(webtest, upload_url, additional_data):
135137

136138

137139
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-
)
140+
user = UserFactory.create(with_verified_primary_email=True, clear_pwd="password")
145141

146142
# Construct the macaroon
147143
dm = MacaroonFactory.create(
@@ -215,13 +211,7 @@ def test_duplicate_file_upload_error(webtest):
215211

216212

217213
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-
)
214+
user = UserFactory.create(with_verified_primary_email=True, clear_pwd="password")
225215

226216
# Construct the macaroon
227217
dm = MacaroonFactory.create(
@@ -270,3 +260,72 @@ def test_invalid_classifier_upload_error(webtest):
270260
status=HTTPStatus.BAD_REQUEST,
271261
)
272262
assert "'This :: Is :: Invalid' is not a valid classifier" in resp.body.decode()
263+
264+
265+
def test_provenance_upload(webtest):
266+
user = UserFactory.create(with_verified_primary_email=True, clear_pwd="password")
267+
project = ProjectFactory.create(name="sampleproject")
268+
RoleFactory.create(user=user, project=project, role_name="Owner")
269+
publisher = GitHubPublisherFactory.create(projects=[project])
270+
271+
# Construct the macaroon. This needs to be based on a Trusted Publisher, which is
272+
# required to upload attestations
273+
dm = MacaroonFactory.create(
274+
oidc_publisher_id=publisher.id,
275+
caveats=[
276+
caveats.OIDCPublisher(oidc_publisher_id=str(publisher.id)),
277+
caveats.ProjectID(project_ids=[str(p.id) for p in publisher.projects]),
278+
],
279+
additional={"oidc": {"ref": "someref", "sha": "somesha"}},
280+
)
281+
282+
m = pymacaroons.Macaroon(
283+
location="localhost",
284+
identifier=str(dm.id),
285+
key=dm.key,
286+
version=pymacaroons.MACAROON_V2,
287+
)
288+
for caveat in dm.caveats:
289+
m.add_first_party_caveat(caveats.serialize(caveat))
290+
serialized_macaroon = f"pypi-{m.serialize()}"
291+
292+
with open(_ASSETS / "sampleproject-3.0.0.tar.gz", "rb") as f:
293+
content = f.read()
294+
295+
with open(
296+
_ASSETS / "sampleproject-3.0.0.tar.gz.publish.attestation",
297+
) as f:
298+
attestation_contents = f.read()
299+
300+
webtest.set_authorization(("Basic", ("__token__", serialized_macaroon)))
301+
webtest.post(
302+
"/legacy/?:action=file_upload",
303+
params={
304+
"name": "sampleproject",
305+
"sha256_digest": (
306+
"117ed88e5db073bb92969a7545745fd977ee85b7019706dd256a64058f70963d"
307+
),
308+
"filetype": "sdist",
309+
"metadata_version": "2.1",
310+
"version": "3.0.0",
311+
"attestations": f"[{attestation_contents}]",
312+
},
313+
upload_files=[("content", "sampleproject-3.0.0.tar.gz", content)],
314+
status=HTTPStatus.OK,
315+
)
316+
317+
assert len(project.releases) == 1
318+
release = project.releases[0]
319+
assert release.files.count() == 1
320+
file_ = project.releases[0].files[0]
321+
assert file_.provenance is not None
322+
provenance = file_.provenance.provenance
323+
assert "attestation_bundles" in provenance
324+
attestation_bundles = provenance["attestation_bundles"]
325+
assert len(attestation_bundles) == 1
326+
bundle = provenance["attestation_bundles"][0]
327+
assert "attestations" in bundle
328+
attestations = bundle["attestations"]
329+
assert len(attestations) == 1
330+
attestation = attestations[0]
331+
assert attestation == json.loads(attestation_contents)

tests/unit/api/test_integrity.py

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
13+
import pretend
14+
import pytest
15+
16+
from warehouse.api import integrity
17+
18+
19+
def test_select_content_type(db_request):
20+
db_request.accept = "application/json"
21+
22+
assert (
23+
integrity._select_content_type(db_request)
24+
== integrity.MIME_PYPI_INTEGRITY_V1_JSON
25+
)
26+
27+
28+
# Backstop; can be removed/changed once this view supports HTML.
29+
@pytest.mark.parametrize(
30+
"content_type",
31+
[integrity.MIME_TEXT_HTML, integrity.MIME_PYPI_INTEGRITY_V1_HTML],
32+
)
33+
def test_provenance_for_file_bad_accept(db_request, content_type):
34+
db_request.accept = content_type
35+
response = integrity.provenance_for_file(pretend.stub(), db_request)
36+
assert response.status_code == 406
37+
assert response.json == {"message": "Request not acceptable"}
38+
39+
40+
def test_provenance_for_file_not_enabled(db_request, monkeypatch):
41+
monkeypatch.setattr(db_request, "flags", pretend.stub(enabled=lambda *a: True))
42+
43+
response = integrity.provenance_for_file(pretend.stub(), db_request)
44+
assert response.status_code == 403
45+
assert response.json == {"message": "Attestations temporarily disabled"}
46+
47+
48+
def test_provenance_for_file_not_present(db_request, monkeypatch):
49+
monkeypatch.setattr(db_request, "flags", pretend.stub(enabled=lambda *a: False))
50+
file = pretend.stub(provenance=None, filename="fake-1.2.3.tar.gz")
51+
52+
response = integrity.provenance_for_file(file, db_request)
53+
assert response.status_code == 404
54+
assert response.json == {"message": "No provenance available for fake-1.2.3.tar.gz"}

0 commit comments

Comments
 (0)