From 17e9d45f9c66f4781cf5dc29c6fdc854f9719437 Mon Sep 17 00:00:00 2001 From: Dustin Ingram Date: Thu, 29 Aug 2024 21:04:55 +0000 Subject: [PATCH] Add a functional test --- ...leproject-3.0.0.tar.gz.publish.attestation | 1 + tests/functional/api/test_simple.py | 86 ++++++++++++++++++- 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 tests/functional/_fixtures/sampleproject-3.0.0.tar.gz.publish.attestation diff --git a/tests/functional/_fixtures/sampleproject-3.0.0.tar.gz.publish.attestation b/tests/functional/_fixtures/sampleproject-3.0.0.tar.gz.publish.attestation new file mode 100644 index 000000000000..9f7a4eb28f53 --- /dev/null +++ b/tests/functional/_fixtures/sampleproject-3.0.0.tar.gz.publish.attestation @@ -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"}} \ No newline at end of file diff --git a/tests/functional/api/test_simple.py b/tests/functional/api/test_simple.py index 636530b0233b..f81dc880f588 100644 --- a/tests/functional/api/test_simple.py +++ b/tests/functional/api/test_simple.py @@ -10,9 +10,21 @@ # See the License for the specific language governing permissions and # limitations under the License. +import base64 +import hashlib from http import HTTPStatus -from ...common.db.packaging import ProjectFactory, ReleaseFactory +from ...common.db.packaging import ( + ProjectFactory, + ReleaseFactory, +) +from ...common.db.accounts import EmailFactory, UserFactory +from ...common.db.packaging import RoleFactory +from ...common.db.macaroons import MacaroonFactory +from ...common.db.oidc import GitHubPublisherFactory +from warehouse.macaroons import caveats + +import pymacaroons def test_simple_api_html(webtest): @@ -31,3 +43,75 @@ 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="$argon2id$v=19$m=1024,t=6,p=6$EiLE2Nsbo9S6N+acs/beGw$ccyZDCZstr1/+Y/1s3BVZHOJaqfBroT0JCieHug281c" # 'password' + ) + 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()}" + + credentials = base64.b64encode( + f"__token__:{serialized_macaroon}".encode("utf-8") + ).decode("utf-8") + + with open("./tests/functional/_fixtures/sampleproject-3.0.0.tar.gz", "rb") as f: + content = f.read() + + with open( + "./tests/functional/_fixtures/sampleproject-3.0.0.tar.gz.publish.attestation", + "r", + ) as f: + attestation = f.read() + + with open( + "./tests/functional/_fixtures/sampleproject-3.0.0.tar.gz.publish.attestation", + "rb", + ) as f: + digest = hashlib.file_digest(f, "sha256") + + expected_hash = digest.hexdigest() + + webtest.post( + "/legacy/?:action=file_upload", + headers={"Authorization": f"Basic {credentials}"}, + 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, + ) + + response = webtest.get("/simple/sampleproject/", status=HTTPStatus.OK) + link = response.html.find("a", text="sampleproject-3.0.0.tar.gz") + assert "data-provenance" in link.attrs + assert link.get("data-provenance") == expected_hash