Skip to content

PEP 740 persistence, take 3 #16624

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
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
1 change: 1 addition & 0 deletions dev/environment
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ BREACHED_EMAILS=warehouse.accounts.NullEmailBreachedService
BREACHED_PASSWORDS=warehouse.accounts.NullPasswordBreachedService

OIDC_BACKEND=warehouse.oidc.services.NullOIDCPublisherService
ATTESTATIONS_BACKEND=warehouse.attestations.services.NullIntegrityService

METRICS_BACKEND=warehouse.metrics.DataDogMetrics host=notdatadog

Expand Down
5 changes: 3 additions & 2 deletions requirements/main.in
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,11 @@ requests
requests-aws4auth
redis>=2.8.0,<6.0.0
rfc3986
rfc8785
sentry-sdk
setuptools
sigstore~=3.0.0
pypi-attestations==0.0.9
sigstore~=3.2.0
pypi-attestations==0.0.11
sqlalchemy[asyncio]>=2.0,<3.0
stdlib-list
stripe
Expand Down
16 changes: 9 additions & 7 deletions requirements/main.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1776,9 +1776,9 @@ pyparsing==3.1.4 \
--hash=sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c \
--hash=sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032
# via linehaul
pypi-attestations==0.0.9 \
--hash=sha256:3bfc07f64a8db0d6e2646720e70df7c7cb01a2936056c764a2cc3268969332f2 \
--hash=sha256:4b38cce5d221c8145cac255bfafe650ec0028d924d2b3572394df8ba8f07a609
pypi-attestations==0.0.11 \
--hash=sha256:b730e6b23874d94da0f3817b1f9dd3ecb6a80d685f62a18ad96e5b0396149ded \
--hash=sha256:e74329074f049568591e300373e12fcd46a35e21723110856546e33bf2949efa
# via -r requirements/main.in
pyqrcode==1.2.1 \
--hash=sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6 \
Expand Down Expand Up @@ -1963,7 +1963,9 @@ rfc3986==2.0.0 \
rfc8785==0.1.3 \
--hash=sha256:167efe3b5cdd09dded9d0cfc8fec1f48f5cd9f8f13b580ada4efcac138925048 \
--hash=sha256:6116062831c62e7ac5d027973a1fe07b601ccd854bca4a2b401938a00a20b0c0
# via sigstore
# via
# -r requirements/main.in
# sigstore
rich==13.8.0 \
--hash=sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc \
--hash=sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4
Expand Down Expand Up @@ -2091,9 +2093,9 @@ sentry-sdk==2.13.0 \
--hash=sha256:6beede8fc2ab4043da7f69d95534e320944690680dd9a963178a49de71d726c6 \
--hash=sha256:8d4a576f7a98eb2fdb40e13106e41f330e5c79d72a68be1316e7852cf4995260
# via -r requirements/main.in
sigstore==3.0.0 \
--hash=sha256:6cc7dc92607c2fd481aada0f3c79e710e4c6086e3beab50b07daa9a50a79d109 \
--hash=sha256:a6a9538a648e112a0c3d8092d3f73a351c7598164764f1e73a6b5ba406a3a0bd
sigstore==3.2.0 \
--hash=sha256:25c8a871a3a6adf959c0cde598ea8bef8794f1a29277d067111eb4ded4ba7f65 \
--hash=sha256:d18508f34febb7775065855e92557fa1c2c16580df88f8e8903b9514438bad44
# via
# -r requirements/main.in
# pypi-attestations
Expand Down
27 changes: 25 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

from jinja2 import Environment, FileSystemLoader
from psycopg.errors import InvalidCatalogName
from pypi_attestations import Attestation, Envelope, VerificationMaterial
from pyramid.i18n import TranslationString
from pyramid.static import ManifestCacheBuster
from pyramid_jinja2 import IJinja2Environment
Expand All @@ -44,6 +45,8 @@
from warehouse.accounts import services as account_services
from warehouse.accounts.interfaces import ITokenService, IUserService
from warehouse.admin.flags import AdminFlag, AdminFlagValue
from warehouse.attestations import services as attestations_services
from warehouse.attestations.interfaces import IIntegrityService
from warehouse.email import services as email_services
from warehouse.email.interfaces import IEmailSender
from warehouse.helpdesk import services as helpdesk_services
Expand Down Expand Up @@ -173,6 +176,7 @@ def pyramid_services(
project_service,
github_oidc_service,
activestate_oidc_service,
integrity_service,
macaroon_service,
helpdesk_service,
):
Expand All @@ -194,6 +198,7 @@ def pyramid_services(
services.register_service(
activestate_oidc_service, IOIDCPublisherService, None, name="activestate"
)
services.register_service(integrity_service, IIntegrityService, None, name="")
services.register_service(macaroon_service, IMacaroonService, None, name="")
services.register_service(helpdesk_service, IHelpDeskService, None)

Expand Down Expand Up @@ -324,6 +329,7 @@ def get_app_config(database, nondefaults=None):
"docs.backend": "warehouse.packaging.services.LocalDocsStorage",
"sponsorlogos.backend": "warehouse.admin.services.LocalSponsorLogoStorage",
"billing.backend": "warehouse.subscriptions.services.MockStripeBillingService",
"attestations.backend": "warehouse.attestations.services.NullIntegrityService",
"billing.api_base": "http://stripe:12111",
"billing.api_version": "2020-08-27",
"mail.backend": "warehouse.email.services.SMTPEmailSender",
Expand Down Expand Up @@ -387,13 +393,11 @@ def get_db_session_for_app_config(app_config):

@pytest.fixture(scope="session")
def app_config(database):

return get_app_config(database)


@pytest.fixture(scope="session")
def app_config_dbsession_from_env(database):

nondefaults = {
"warehouse.db_create_session": lambda r: r.environ.get("warehouse.db_session")
}
Expand Down Expand Up @@ -539,6 +543,25 @@ def activestate_oidc_service(db_session):
)


@pytest.fixture
def dummy_attestation():
return Attestation(
version=1,
verification_material=VerificationMaterial(
certificate="somebase64string", transparency_entries=[dict()]
),
envelope=Envelope(
statement="somebase64string",
signature="somebase64string",
),
)


@pytest.fixture
def integrity_service(db_session):
return attestations_services.NullIntegrityService(db_session)


@pytest.fixture
def macaroon_service(db_session):
return macaroon_services.DatabaseMacaroonService(db_session)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"version":1,"verification_material":{"certificate":"MIIC6zCCAnGgAwIBAgIUFgmhIYx8gvBGePCTacG/4kbBdRwwCgYIKoZIzj0EAwMwNzEVMBMGA1UE\nChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjQwODI5\nMTcwOTM5WhcNMjQwODI5MTcxOTM5WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtGrMPml4\nOtsRJ3Z6qRahs0kHCZxP4n9fvrJE957WVxgAGg4k6a1PbRJY9nT9wKpRrZmKV++AgA9ndhdruXXa\nAKOCAZAwggGMMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQU\nosNvhYEuTPfgyU/dZfu93lFGRNswHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wQAYD\nVR0RAQH/BDYwNIEyOTE5NDM2MTU4MjM2LWNvbXB1dGVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3Vu\ndC5jb20wKQYKKwYBBAGDvzABAQQbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMCsGCisGAQQB\ng78wAQgEHQwbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMIGKBgorBgEEAdZ5AgQCBHwEegB4\nAHYA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4AAAGRnx0/aQAABAMARzBFAiBogvcK\nHIIR9FcX1vQgDhGtAl0XQoMRiEB3OdUWO94P1gIhANdJlyISdtvVrHes25dWKTLepy+IzQmzfQU/\nS7cxWHmOMAoGCCqGSM49BAMDA2gAMGUCMGe2xTiuenbjdt1d2e4IaCiwRh2G4KAtyujRESSSUbpu\nGme/o9ouiApeONBv2CvvGAIxAOEkAGFO3aALE3IPNosxqaz9MbqJOdmYhB1Cz1D7xbFc/m243VxJ\nWxaC/uOFEpyiYQ==\n","transparency_entries":[{"logIndex":"125970014","logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"kindVersion":{"kind":"dsse","version":"0.0.1"},"integratedTime":"1724951379","inclusionPromise":{"signedEntryTimestamp":"MEUCIQCHrKFTeXNY432S0bUSBS69S8d5JnNcDXa41q6OEvxEwgIgaZstc5Jpm0IgwFC7RDTXYEAKk+3aG/MkRkaPdJdyn8U="},"inclusionProof":{"logIndex":"4065752","rootHash":"7jVDF3UNUZVEU85ffETQ3WKfXhOoMi4cgytJM250HTk=","treeSize":"4065754","hashes":["NwJgWJoxjearbnEIT9bnWXpzo0LGNrR1cpWId0g66rE=","kLjpW3Eh7pQJNOvyntghzF57tcfqk2IzX7cqiBDgGf8=","FW8y9LQ1i3q+MnbeGJipKGl4VfX1zRBOD7TmhbEw7uI=","mKcbGJDJ/+buNbXy9Eyv94nVoAyUauuIlN3cJg3qSBY=","5VytqqAHhfRkRWMrY43UXWCnRBb7JwElMlKpY5JueBc=","mZJnD39LTKdis2wUTz1OOMx3r7HwgJh9rnb2VwiPzts=","MXZOQFJFiOjREF0xwMOCXu29HwTchjTtl/BeFoI51wY=","g8zCkHnLwO3LojK7g5AnqE8ezSNRnCSz9nCL5GD3a8A=","RrZsD/RSxNoujlvq/MsCEvLSkKZfv0jmQM9Kp7qbJec=","QxmVWsbTp4cClxuAkuT51UH2EY7peHMVGKq7+b+cGwQ=","Q2LAtNzOUh+3PfwfMyNxYb06fTQmF3VeTT6Fr6Upvfc=","ftwAu6v62WFDoDmcZ1JKfrRPrvuiIw5v3BvRsgQj7N8="],"checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n4065754\n7jVDF3UNUZVEU85ffETQ3WKfXhOoMi4cgytJM250HTk=\n\n— rekor.sigstore.dev wNI9ajBGAiEAhMomhZHOTNB5CVPO98CMXCv01ZlIF+C+CgzraAB01r8CIQCEuXbv6aqguUpB/ig5eXRIbarvxLXkg3nX48DzambktQ==\n"}},"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiOWRiNGJjMzE3MTgyZWI3NzljNDIyY2Q0NGI2ZDdlYTk5ZWM1M2Q3M2JiY2ZjZWVmZTIyNWVlYjQ3NTQyMjc4OCJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjlkYjY0MjlhOTkzZGFiYTI4NzAwODk2ZTY2MzNjNzkxYWE0MDM3ODQ4NjJiYzY2MDBkM2E4NjYwMGQzYjA1NjMifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVVQ0lCaGlOL25NR0w3aHpZQk9QQjlUTGtuaEdTZEtuQ0Q0ekI3TDV5ZXc0QmJ3QWlFQXJzOHl6MCtCT2NnSEtzS0JzTXVOeVlhREdaRTBVV0JuMEdwNVpGMzUvU2M9IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VNMmVrTkRRVzVIWjBGM1NVSkJaMGxWUm1kdGFFbFplRGhuZGtKSFpWQkRWR0ZqUnk4MGEySkNaRkozZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwUmQwOUVTVFZOVkdOM1QxUk5OVmRvWTA1TmFsRjNUMFJKTlUxVVkzaFBWRTAxVjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVjBSM0pOVUcxc05FOTBjMUpLTTFvMmNWSmhhSE13YTBoRFduaFFORzQ1Wm5aeVNrVUtPVFUzVjFaNFowRkhaelJyTm1FeFVHSlNTbGs1YmxRNWQwdHdVbkphYlV0V0t5dEJaMEU1Ym1Sb1pISjFXRmhoUVV0UFEwRmFRWGRuWjBkTlRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVnZjMDUyQ21oWlJYVlVVR1puZVZVdlpGcG1kVGt6YkVaSFVrNXpkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMUZCV1VSV1VqQlNRVkZJTDBKRVdYZE9TVVY1VDFSRk5VNUVUVEpOVkZVMFRXcE5Na3hYVG5aaVdFSXhaRWRXUVZwSFZqSmFWM2gyWTBkV2VRcE1iV1I2V2xoS01tRlhUbXhaVjA1cVlqTldkV1JETldwaU1qQjNTMUZaUzB0M1dVSkNRVWRFZG5wQlFrRlJVV0poU0ZJd1kwaE5Oa3g1T1doWk1rNTJDbVJYTlRCamVUVnVZakk1Ym1KSFZYVlpNamwwVFVOelIwTnBjMGRCVVZGQ1p6YzRkMEZSWjBWSVVYZGlZVWhTTUdOSVRUWk1lVGxvV1RKT2RtUlhOVEFLWTNrMWJtSXlPVzVpUjFWMVdUSTVkRTFKUjB0Q1oyOXlRbWRGUlVGa1dqVkJaMUZEUWtoM1JXVm5RalJCU0ZsQk0xUXdkMkZ6WWtoRlZFcHFSMUkwWXdwdFYyTXpRWEZLUzFoeWFtVlFTek12YURSd2VXZERPSEEzYnpSQlFVRkhVbTU0TUM5aFVVRkJRa0ZOUVZKNlFrWkJhVUp2WjNaalMwaEpTVkk1Um1OWUNqRjJVV2RFYUVkMFFXd3dXRkZ2VFZKcFJVSXpUMlJWVjA4NU5GQXhaMGxvUVU1a1NteDVTVk5rZEhaV2NraGxjekkxWkZkTFZFeGxjSGtyU1hwUmJYb0tabEZWTDFNM1kzaFhTRzFQVFVGdlIwTkRjVWRUVFRRNVFrRk5SRUV5WjBGTlIxVkRUVWRsTW5oVWFYVmxibUpxWkhReFpESmxORWxoUTJsM1VtZ3lSd28wUzBGMGVYVnFVa1ZUVTFOVlluQjFSMjFsTDI4NWIzVnBRWEJsVDA1Q2RqSkRkblpIUVVsNFFVOUZhMEZIUms4ellVRk1SVE5KVUU1dmMzaHhZWG81Q2sxaWNVcFBaRzFaYUVJeFEzb3hSRGQ0WWtaakwyMHlORE5XZUVwWGVHRkRMM1ZQUmtWd2VXbFpVVDA5Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn1dfX0="}]},"envelope":{"statement":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJu\nYW1lIjoic2FtcGxlcHJvamVjdC0zLjAuMC50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiMTE3\nZWQ4OGU1ZGIwNzNiYjkyOTY5YTc1NDU3NDVmZDk3N2VlODViNzAxOTcwNmRkMjU2YTY0MDU4Zjcw\nOTYzZCJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2RvY3MucHlwaS5vcmcvYXR0ZXN0YXRp\nb25zL3B1Ymxpc2gvdjEiLCJwcmVkaWNhdGUiOm51bGx9\n","signature":"MEUCIBhiN/nMGL7hzYBOPB9TLknhGSdKnCD4zB7L5yew4BbwAiEArs8yz0+BOcgHKsKBsMuNyYaD\nGZE0UWBn0Gp5ZF35/Sc=\n"}}
98 changes: 97 additions & 1 deletion tests/functional/api/test_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,19 @@
# limitations under the License.

from http import HTTPStatus
from pathlib import Path

from ...common.db.packaging import ProjectFactory, ReleaseFactory
import pymacaroons

from warehouse.macaroons import caveats

from ...common.db.accounts import EmailFactory, UserFactory
from ...common.db.macaroons import MacaroonFactory
from ...common.db.oidc import GitHubPublisherFactory
from ...common.db.packaging import ProjectFactory, ReleaseFactory, RoleFactory

_HERE = Path(__file__).parent
_ASSETS = _HERE.parent / "_fixtures"


def test_simple_api_html(webtest):
Expand All @@ -31,3 +42,88 @@ def test_simple_api_detail(webtest):
assert resp.content_type == "text/html"
assert "X-PyPI-Last-Serial" in resp.headers
assert f"Links for {project.normalized_name}" in resp.text


def test_simple_attestations_from_upload(webtest):
user = UserFactory.create(
password=( # 'password'
"$argon2id$v=19$m=1024,t=6,p=6$EiLE2Nsbo9S6N+acs/beGw$ccyZDCZstr1/+Y/1s3BVZ"
"HOJaqfBroT0JCieHug281c"
)
)
EmailFactory.create(user=user, verified=True)
project = ProjectFactory.create(name="sampleproject")
RoleFactory.create(user=user, project=project, role_name="Owner")
publisher = GitHubPublisherFactory.create(projects=[project])

# Construct the macaroon. This needs to be based on a Trusted Publisher, which is
# required to upload attestations
dm = MacaroonFactory.create(
oidc_publisher_id=publisher.id,
caveats=[
caveats.OIDCPublisher(oidc_publisher_id=str(publisher.id)),
caveats.ProjectID(project_ids=[str(p.id) for p in publisher.projects]),
],
additional={"oidc": {"ref": "someref", "sha": "somesha"}},
)

m = pymacaroons.Macaroon(
location="localhost",
identifier=str(dm.id),
key=dm.key,
version=pymacaroons.MACAROON_V2,
)
for caveat in dm.caveats:
m.add_first_party_caveat(caveats.serialize(caveat))
serialized_macaroon = f"pypi-{m.serialize()}"

with open(_ASSETS / "sampleproject-3.0.0.tar.gz", "rb") as f:
content = f.read()

with open(
_ASSETS / "sampleproject-3.0.0.tar.gz.publish.attestation",
) as f:
attestation = f.read()

webtest.set_authorization(("Basic", ("__token__", serialized_macaroon)))
webtest.post(
"/legacy/?:action=file_upload",
params={
"name": "sampleproject",
"sha256_digest": (
"117ed88e5db073bb92969a7545745fd977ee85b7019706dd256a64058f70963d"
),
"filetype": "sdist",
"metadata_version": "2.1",
"version": "3.0.0",
"attestations": f"[{attestation}]",
},
upload_files=[("content", "sampleproject-3.0.0.tar.gz", content)],
status=HTTPStatus.OK,
)

assert len(project.releases) == 1
assert project.releases[0].files.count() == 1
assert project.releases[0].files[0].provenance is not None

expected_provenance = project.releases[0].files[0].provenance.provenance_digest
expected_filename = "sampleproject-3.0.0.tar.gz"

response = webtest.get("/simple/sampleproject/", status=HTTPStatus.OK)
link = response.html.find("a", text=expected_filename)

assert "data-provenance" in link.attrs
assert link.get("data-provenance") == expected_provenance

response = webtest.get(
"/simple/sampleproject/",
headers={"Accept": "application/vnd.pypi.simple.v1+json"},
status=HTTPStatus.OK,
)

assert response.content_type == "application/vnd.pypi.simple.v1+json"

json_content = response.json
assert len(json_content["files"]) == 1
assert json_content["files"][0]["filename"] == expected_filename
assert json_content["files"][0]["provenance"] == expected_provenance
73 changes: 73 additions & 0 deletions tests/unit/api/test_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from pyramid.httpexceptions import HTTPMovedPermanently
from pyramid.testing import DummyRequest

from tests.common.db.oidc import GitHubPublisherFactory
from warehouse.api import simple
from warehouse.packaging.utils import API_VERSION

Expand Down Expand Up @@ -286,6 +287,7 @@ def test_with_files_no_serial(self, db_request, content_type, renderer_override)
"upload-time": f.upload_time.isoformat() + "Z",
"data-dist-info-metadata": False,
"core-metadata": False,
"provenance": None,
}
for f in files
],
Expand Down Expand Up @@ -334,6 +336,7 @@ def test_with_files_with_serial(self, db_request, content_type, renderer_overrid
"upload-time": f.upload_time.isoformat() + "Z",
"data-dist-info-metadata": False,
"core-metadata": False,
"provenance": None,
}
for f in files
],
Expand Down Expand Up @@ -427,6 +430,7 @@ def test_with_files_with_version_multi_digit(
if f.metadata_file_sha256_digest is not None
else False
),
"provenance": None,
}
for f in files
],
Expand All @@ -439,6 +443,75 @@ def test_with_files_with_version_multi_digit(
if renderer_override is not None:
assert db_request.override_renderer == renderer_override

def test_with_files_varying_provenance(
self, db_request, integrity_service, dummy_attestation
):
db_request.oidc_publisher = GitHubPublisherFactory.create()

project = ProjectFactory.create()
release = ReleaseFactory.create(project=project, version="1.0.0")

# wheel with provenance, sdist with no provenance
wheel = FileFactory.create(
release=release,
filename=f"{project.name}-1.0.0.whl",
packagetype="bdist_wheel",
metadata_file_sha256_digest="deadbeefdeadbeefdeadbeefdeadbeef",
)

provenance = integrity_service.build_provenance(
db_request, wheel, [dummy_attestation]
)
assert wheel.provenance == provenance
assert wheel.provenance.provenance_digest is not None

sdist = FileFactory.create(
release=release,
filename=f"{project.name}-1.0.0.tar.gz",
packagetype="sdist",
)

files = [sdist, wheel]

urls_iter = (f"/file/{f.filename}" for f in files)
db_request.matchdict["name"] = project.normalized_name
db_request.route_url = lambda *a, **kw: next(urls_iter)
user = UserFactory.create()
je = JournalEntryFactory.create(name=project.name, submitted_by=user)

assert simple.simple_detail(project, db_request) == {
"meta": {"_last-serial": je.id, "api-version": API_VERSION},
"name": project.normalized_name,
"versions": ["1.0.0"],
"files": [
{
"filename": f.filename,
"url": f"/file/{f.filename}",
"hashes": {"sha256": f.sha256_digest},
"requires-python": f.requires_python,
"yanked": False,
"size": f.size,
"upload-time": f.upload_time.isoformat() + "Z",
"data-dist-info-metadata": (
{"sha256": "deadbeefdeadbeefdeadbeefdeadbeef"}
if f.metadata_file_sha256_digest is not None
else False
),
"core-metadata": (
{"sha256": "deadbeefdeadbeefdeadbeefdeadbeef"}
if f.metadata_file_sha256_digest is not None
else False
),
"provenance": (
f.provenance.provenance_digest
if f.provenance is not None
else None
),
}
for f in files
],
}

def test_with_files_quarantined_omitted_from_index(self, db_request):
db_request.accept = "text/html"
project = ProjectFactory.create(lifecycle_status="quarantine-enter")
Expand Down
11 changes: 11 additions & 0 deletions tests/unit/attestations/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
Loading