From af6432f046c950d1bdd4fcbc5326fc27dc834916 Mon Sep 17 00:00:00 2001 From: Facundo Tuesca Date: Mon, 13 May 2024 18:31:44 +0200 Subject: [PATCH] Add support for uploading attestations in legacy API --- requirements/dev.txt | 4 +- requirements/main.in | 2 + requirements/main.txt | 328 +++++++++++++++----------- tests/unit/forklift/test_legacy.py | 310 ++++++++++++++++++++++++ tests/unit/oidc/models/test_github.py | 26 ++ warehouse/forklift/legacy.py | 49 ++++ warehouse/oidc/models/_core.py | 12 + warehouse/oidc/models/github.py | 25 ++ 8 files changed, 612 insertions(+), 144 deletions(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index b9b34ac968f8..44131059f86f 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -3,4 +3,6 @@ hupper>=1.9 pip-tools>=1.0 pyramid_debugtoolbar>=2.5 pip-api -repository-service-tuf \ No newline at end of file +# TODO: re-add before merging, once repository-service-tuf releases a new version. +# The current version pins tuf==3.1.0, which conflicts with our `sigstore` dependency (`tuf==4.0.0`) +#repository-service-tuf \ No newline at end of file diff --git a/requirements/main.in b/requirements/main.in index b5e9bcb5de2c..8b68d6880f05 100644 --- a/requirements/main.in +++ b/requirements/main.in @@ -62,6 +62,8 @@ redis>=2.8.0,<6.0.0 rfc3986 sentry-sdk setuptools +sigstore==3.0.0rc2 +pypi-attestation-models==0.0.1rc2 sqlalchemy[asyncio]>=2.0,<3.0 stdlib-list stripe diff --git a/requirements/main.txt b/requirements/main.txt index 04c093cdcbca..8000581f7bea 100644 --- a/requirements/main.txt +++ b/requirements/main.txt @@ -102,6 +102,10 @@ bcrypt==4.1.3 \ --hash=sha256:f44a97780677e7ac0ca393bd7982b19dbbd8d7228c1afe10b128fd9550eef5f1 \ --hash=sha256:f5698ce5292a4e4b9e5861f7e53b1d89242ad39d54c3da451a93cac17b61921a # via -r requirements/main.in +betterproto==2.0.0b6 \ + --hash=sha256:720ae92697000f6fcf049c69267d957f0871654c8b0d7458906607685daee784 \ + --hash=sha256:a0839ec165d110a69d0d116f4d0e2bec8d186af4db826257931f0831dab73fcf + # via sigstore-protobuf-specs billiard==4.2.0 \ --hash=sha256:07aa978b308f334ff8282bd4a746e681b3513db5c9a514cbdd810cbbdc19714d \ --hash=sha256:9a3c3184cb275aa17a732f93f65b20c525d3d9f253722d26a82194803ade5a2c @@ -454,6 +458,8 @@ cryptography==42.0.7 \ # -r requirements/main.in # pyjwt # pyopenssl + # pypi-attestation-models + # sigstore # webauthn cssselect==1.2.0 \ --hash=sha256:666b19839cfaddb9ce9d36bfe4c969132c647b92fc9088c4e23f786b30f1b3dc \ @@ -496,7 +502,9 @@ elasticsearch-dsl==7.4.1 \ email-validator==2.1.1 \ --hash=sha256:200a70680ba08904be6d1eef729205cc0d687634399a5924d842533efb824b84 \ --hash=sha256:97d882d174e2a65732fb43bfce81a3a834cbc1bde8bf419e30ef5ea976370a05 - # via wtforms + # via + # pydantic + # wtforms first==2.0.2 \ --hash=sha256:8d8e46e115ea8ac652c76123c0865e3ff18372aef6f03c22809ceefcea9dec86 \ --hash=sha256:ff285b08c55f8c97ce4ea7012743af2495c9f1291785f163722bd36f6af6d3bf @@ -736,6 +744,13 @@ grpcio-status==1.62.1 \ --hash=sha256:3431c8abbab0054912c41df5c72f03ddf3b7a67be8a287bb3c18a3456f96ff77 \ --hash=sha256:af0c3ab85da31669f21749e8d53d669c061ebc6ce5637be49a46edcb7aa8ab17 # via google-api-core +grpclib==0.4.7 \ + --hash=sha256:2988ef57c02b22b7a2e8e961792c41ccf97efc2ace91ae7a5b0de03c363823c3 + # via betterproto +h2==4.1.0 \ + --hash=sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d \ + --hash=sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb + # via grpclib hiredis==2.3.2 \ --hash=sha256:01b6c24c0840ac7afafbc4db236fd55f56a9a0919a215c25a238f051781f4772 \ --hash=sha256:02fc71c8333586871602db4774d3a3e403b4ccf6446dc4603ec12df563127cee \ @@ -847,6 +862,10 @@ hiredis==2.3.2 \ --hash=sha256:f9f606e810858207d4b4287b4ef0dc622c2aa469548bf02b59dcc616f134f811 \ --hash=sha256:fa45f7d771094b8145af10db74704ab0f698adb682fbf3721d8090f90e42cc49 # via -r requirements/main.in +hpack==4.0.0 \ + --hash=sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c \ + --hash=sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095 + # via h2 html5lib==1.1 \ --hash=sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d \ --hash=sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f @@ -859,6 +878,14 @@ hupper==1.12.1 \ --hash=sha256:06bf54170ff4ecf4c84ad5f188dee3901173ab449c2608ad05b9bfd6b13e32eb \ --hash=sha256:e872b959f09d90be5fb615bd2e62de89a0b57efc037bdf9637fb09cdf8552b19 # via pyramid +hyperframe==6.0.1 \ + --hash=sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15 \ + --hash=sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914 + # via h2 +id==1.4.0 \ + --hash=sha256:23c06772e8bd3e3a44ee3f167868bf5a8e385b0c1e2cc707ad36eb7486b4765b \ + --hash=sha256:a0391117c98fa9851ebd2b22df0dc6fd6aacbd89a4ec95c173f1311ca9bb7329 + # via sigstore idna==3.7 \ --hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \ --hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0 @@ -967,280 +994,142 @@ logfury==1.0.1 \ # via b2sdk lxml==5.2.2 \ --hash=sha256:02437fb7308386867c8b7b0e5bc4cd4b04548b1c5d089ffb8e7b31009b961dc3 \ - --hash=sha256:02437fb7308386867c8b7b0e5bc4cd4b04548b1c5d089ffb8e7b31009b961dc3 \ - --hash=sha256:02f6a8eb6512fdc2fd4ca10a49c341c4e109aa6e9448cc4859af5b949622715a \ --hash=sha256:02f6a8eb6512fdc2fd4ca10a49c341c4e109aa6e9448cc4859af5b949622715a \ --hash=sha256:05f8757b03208c3f50097761be2dea0aba02e94f0dc7023ed73a7bb14ff11eb0 \ - --hash=sha256:05f8757b03208c3f50097761be2dea0aba02e94f0dc7023ed73a7bb14ff11eb0 \ - --hash=sha256:06668e39e1f3c065349c51ac27ae430719d7806c026fec462e5693b08b95696b \ --hash=sha256:06668e39e1f3c065349c51ac27ae430719d7806c026fec462e5693b08b95696b \ --hash=sha256:07542787f86112d46d07d4f3c4e7c760282011b354d012dc4141cc12a68cef5f \ - --hash=sha256:07542787f86112d46d07d4f3c4e7c760282011b354d012dc4141cc12a68cef5f \ --hash=sha256:08ea0f606808354eb8f2dfaac095963cb25d9d28e27edcc375d7b30ab01abbf6 \ - --hash=sha256:08ea0f606808354eb8f2dfaac095963cb25d9d28e27edcc375d7b30ab01abbf6 \ - --hash=sha256:0969e92af09c5687d769731e3f39ed62427cc72176cebb54b7a9d52cc4fa3b73 \ --hash=sha256:0969e92af09c5687d769731e3f39ed62427cc72176cebb54b7a9d52cc4fa3b73 \ --hash=sha256:0a028b61a2e357ace98b1615fc03f76eb517cc028993964fe08ad514b1e8892d \ - --hash=sha256:0a028b61a2e357ace98b1615fc03f76eb517cc028993964fe08ad514b1e8892d \ - --hash=sha256:0b3f5016e00ae7630a4b83d0868fca1e3d494c78a75b1c7252606a3a1c5fc2ad \ --hash=sha256:0b3f5016e00ae7630a4b83d0868fca1e3d494c78a75b1c7252606a3a1c5fc2ad \ --hash=sha256:13e69be35391ce72712184f69000cda04fc89689429179bc4c0ae5f0b7a8c21b \ - --hash=sha256:13e69be35391ce72712184f69000cda04fc89689429179bc4c0ae5f0b7a8c21b \ --hash=sha256:16a8326e51fcdffc886294c1e70b11ddccec836516a343f9ed0f82aac043c24a \ - --hash=sha256:16a8326e51fcdffc886294c1e70b11ddccec836516a343f9ed0f82aac043c24a \ - --hash=sha256:19b4e485cd07b7d83e3fe3b72132e7df70bfac22b14fe4bf7a23822c3a35bff5 \ --hash=sha256:19b4e485cd07b7d83e3fe3b72132e7df70bfac22b14fe4bf7a23822c3a35bff5 \ --hash=sha256:1a2569a1f15ae6c8c64108a2cd2b4a858fc1e13d25846be0666fc144715e32ab \ - --hash=sha256:1a2569a1f15ae6c8c64108a2cd2b4a858fc1e13d25846be0666fc144715e32ab \ - --hash=sha256:1a7aca7964ac4bb07680d5c9d63b9d7028cace3e2d43175cb50bba8c5ad33316 \ --hash=sha256:1a7aca7964ac4bb07680d5c9d63b9d7028cace3e2d43175cb50bba8c5ad33316 \ --hash=sha256:1d8a701774dfc42a2f0b8ccdfe7dbc140500d1049e0632a611985d943fcf12df \ - --hash=sha256:1d8a701774dfc42a2f0b8ccdfe7dbc140500d1049e0632a611985d943fcf12df \ - --hash=sha256:1e275ea572389e41e8b039ac076a46cb87ee6b8542df3fff26f5baab43713bca \ --hash=sha256:1e275ea572389e41e8b039ac076a46cb87ee6b8542df3fff26f5baab43713bca \ --hash=sha256:2304d3c93f2258ccf2cf7a6ba8c761d76ef84948d87bf9664e14d203da2cd264 \ - --hash=sha256:2304d3c93f2258ccf2cf7a6ba8c761d76ef84948d87bf9664e14d203da2cd264 \ - --hash=sha256:23441e2b5339bc54dc949e9e675fa35efe858108404ef9aa92f0456929ef6fe8 \ --hash=sha256:23441e2b5339bc54dc949e9e675fa35efe858108404ef9aa92f0456929ef6fe8 \ --hash=sha256:23cfafd56887eaed93d07bc4547abd5e09d837a002b791e9767765492a75883f \ - --hash=sha256:23cfafd56887eaed93d07bc4547abd5e09d837a002b791e9767765492a75883f \ --hash=sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b \ - --hash=sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b \ - --hash=sha256:2eb2227ce1ff998faf0cd7fe85bbf086aa41dfc5af3b1d80867ecfe75fb68df3 \ --hash=sha256:2eb2227ce1ff998faf0cd7fe85bbf086aa41dfc5af3b1d80867ecfe75fb68df3 \ --hash=sha256:2fb0ba3e8566548d6c8e7dd82a8229ff47bd8fb8c2da237607ac8e5a1b8312e5 \ - --hash=sha256:2fb0ba3e8566548d6c8e7dd82a8229ff47bd8fb8c2da237607ac8e5a1b8312e5 \ - --hash=sha256:303f540ad2dddd35b92415b74b900c749ec2010e703ab3bfd6660979d01fd4ed \ --hash=sha256:303f540ad2dddd35b92415b74b900c749ec2010e703ab3bfd6660979d01fd4ed \ --hash=sha256:339ee4a4704bc724757cd5dd9dc8cf4d00980f5d3e6e06d5847c1b594ace68ab \ - --hash=sha256:339ee4a4704bc724757cd5dd9dc8cf4d00980f5d3e6e06d5847c1b594ace68ab \ --hash=sha256:33ce9e786753743159799fdf8e92a5da351158c4bfb6f2db0bf31e7892a1feb5 \ - --hash=sha256:33ce9e786753743159799fdf8e92a5da351158c4bfb6f2db0bf31e7892a1feb5 \ - --hash=sha256:343ab62e9ca78094f2306aefed67dcfad61c4683f87eee48ff2fd74902447726 \ --hash=sha256:343ab62e9ca78094f2306aefed67dcfad61c4683f87eee48ff2fd74902447726 \ --hash=sha256:34e17913c431f5ae01d8658dbf792fdc457073dcdfbb31dc0cc6ab256e664a8d \ - --hash=sha256:34e17913c431f5ae01d8658dbf792fdc457073dcdfbb31dc0cc6ab256e664a8d \ - --hash=sha256:364d03207f3e603922d0d3932ef363d55bbf48e3647395765f9bfcbdf6d23632 \ --hash=sha256:364d03207f3e603922d0d3932ef363d55bbf48e3647395765f9bfcbdf6d23632 \ --hash=sha256:38b67afb0a06b8575948641c1d6d68e41b83a3abeae2ca9eed2ac59892b36706 \ - --hash=sha256:38b67afb0a06b8575948641c1d6d68e41b83a3abeae2ca9eed2ac59892b36706 \ --hash=sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8 \ - --hash=sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8 \ - --hash=sha256:3b019d4ee84b683342af793b56bb35034bd749e4cbdd3d33f7d1107790f8c472 \ --hash=sha256:3b019d4ee84b683342af793b56bb35034bd749e4cbdd3d33f7d1107790f8c472 \ --hash=sha256:3b6a30a9ab040b3f545b697cb3adbf3696c05a3a68aad172e3fd7ca73ab3c835 \ - --hash=sha256:3b6a30a9ab040b3f545b697cb3adbf3696c05a3a68aad172e3fd7ca73ab3c835 \ - --hash=sha256:3d1e35572a56941b32c239774d7e9ad724074d37f90c7a7d499ab98761bd80cf \ --hash=sha256:3d1e35572a56941b32c239774d7e9ad724074d37f90c7a7d499ab98761bd80cf \ --hash=sha256:3d98de734abee23e61f6b8c2e08a88453ada7d6486dc7cdc82922a03968928db \ - --hash=sha256:3d98de734abee23e61f6b8c2e08a88453ada7d6486dc7cdc82922a03968928db \ - --hash=sha256:453d037e09a5176d92ec0fd282e934ed26d806331a8b70ab431a81e2fbabf56d \ --hash=sha256:453d037e09a5176d92ec0fd282e934ed26d806331a8b70ab431a81e2fbabf56d \ --hash=sha256:45f9494613160d0405682f9eee781c7e6d1bf45f819654eb249f8f46a2c22545 \ - --hash=sha256:45f9494613160d0405682f9eee781c7e6d1bf45f819654eb249f8f46a2c22545 \ - --hash=sha256:4820c02195d6dfb7b8508ff276752f6b2ff8b64ae5d13ebe02e7667e035000b9 \ --hash=sha256:4820c02195d6dfb7b8508ff276752f6b2ff8b64ae5d13ebe02e7667e035000b9 \ --hash=sha256:49095a38eb333aaf44c06052fd2ec3b8f23e19747ca7ec6f6c954ffea6dbf7be \ - --hash=sha256:49095a38eb333aaf44c06052fd2ec3b8f23e19747ca7ec6f6c954ffea6dbf7be \ --hash=sha256:4aefd911793b5d2d7a921233a54c90329bf3d4a6817dc465f12ffdfe4fc7b8fe \ - --hash=sha256:4aefd911793b5d2d7a921233a54c90329bf3d4a6817dc465f12ffdfe4fc7b8fe \ - --hash=sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905 \ --hash=sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905 \ --hash=sha256:4c30a2f83677876465f44c018830f608fa3c6a8a466eb223535035fbc16f3438 \ - --hash=sha256:4c30a2f83677876465f44c018830f608fa3c6a8a466eb223535035fbc16f3438 \ - --hash=sha256:50127c186f191b8917ea2fb8b206fbebe87fd414a6084d15568c27d0a21d60db \ --hash=sha256:50127c186f191b8917ea2fb8b206fbebe87fd414a6084d15568c27d0a21d60db \ --hash=sha256:50ccb5d355961c0f12f6cf24b7187dbabd5433f29e15147a67995474f27d1776 \ - --hash=sha256:50ccb5d355961c0f12f6cf24b7187dbabd5433f29e15147a67995474f27d1776 \ --hash=sha256:519895c99c815a1a24a926d5b60627ce5ea48e9f639a5cd328bda0515ea0f10c \ - --hash=sha256:519895c99c815a1a24a926d5b60627ce5ea48e9f639a5cd328bda0515ea0f10c \ - --hash=sha256:54401c77a63cc7d6dc4b4e173bb484f28a5607f3df71484709fe037c92d4f0ed \ --hash=sha256:54401c77a63cc7d6dc4b4e173bb484f28a5607f3df71484709fe037c92d4f0ed \ --hash=sha256:546cf886f6242dff9ec206331209db9c8e1643ae642dea5fdbecae2453cb50fd \ - --hash=sha256:546cf886f6242dff9ec206331209db9c8e1643ae642dea5fdbecae2453cb50fd \ - --hash=sha256:55ce6b6d803890bd3cc89975fca9de1dff39729b43b73cb15ddd933b8bc20484 \ --hash=sha256:55ce6b6d803890bd3cc89975fca9de1dff39729b43b73cb15ddd933b8bc20484 \ --hash=sha256:56793b7a1a091a7c286b5f4aa1fe4ae5d1446fe742d00cdf2ffb1077865db10d \ - --hash=sha256:56793b7a1a091a7c286b5f4aa1fe4ae5d1446fe742d00cdf2ffb1077865db10d \ --hash=sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6 \ - --hash=sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6 \ - --hash=sha256:5b8c041b6265e08eac8a724b74b655404070b636a8dd6d7a13c3adc07882ef30 \ --hash=sha256:5b8c041b6265e08eac8a724b74b655404070b636a8dd6d7a13c3adc07882ef30 \ --hash=sha256:5e097646944b66207023bc3c634827de858aebc226d5d4d6d16f0b77566ea182 \ - --hash=sha256:5e097646944b66207023bc3c634827de858aebc226d5d4d6d16f0b77566ea182 \ - --hash=sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61 \ --hash=sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61 \ --hash=sha256:610b5c77428a50269f38a534057444c249976433f40f53e3b47e68349cca1425 \ - --hash=sha256:610b5c77428a50269f38a534057444c249976433f40f53e3b47e68349cca1425 \ - --hash=sha256:625e3ef310e7fa3a761d48ca7ea1f9d8718a32b1542e727d584d82f4453d5eeb \ --hash=sha256:625e3ef310e7fa3a761d48ca7ea1f9d8718a32b1542e727d584d82f4453d5eeb \ --hash=sha256:657a972f46bbefdbba2d4f14413c0d079f9ae243bd68193cb5061b9732fa54c1 \ - --hash=sha256:657a972f46bbefdbba2d4f14413c0d079f9ae243bd68193cb5061b9732fa54c1 \ - --hash=sha256:69ab77a1373f1e7563e0fb5a29a8440367dec051da6c7405333699d07444f511 \ --hash=sha256:69ab77a1373f1e7563e0fb5a29a8440367dec051da6c7405333699d07444f511 \ --hash=sha256:6a520b4f9974b0a0a6ed73c2154de57cdfd0c8800f4f15ab2b73238ffed0b36e \ - --hash=sha256:6a520b4f9974b0a0a6ed73c2154de57cdfd0c8800f4f15ab2b73238ffed0b36e \ --hash=sha256:6d68ce8e7b2075390e8ac1e1d3a99e8b6372c694bbe612632606d1d546794207 \ - --hash=sha256:6d68ce8e7b2075390e8ac1e1d3a99e8b6372c694bbe612632606d1d546794207 \ - --hash=sha256:6dcc3d17eac1df7859ae01202e9bb11ffa8c98949dcbeb1069c8b9a75917e01b \ --hash=sha256:6dcc3d17eac1df7859ae01202e9bb11ffa8c98949dcbeb1069c8b9a75917e01b \ --hash=sha256:6dfdc2bfe69e9adf0df4915949c22a25b39d175d599bf98e7ddf620a13678585 \ - --hash=sha256:6dfdc2bfe69e9adf0df4915949c22a25b39d175d599bf98e7ddf620a13678585 \ - --hash=sha256:739e36ef7412b2bd940f75b278749106e6d025e40027c0b94a17ef7968d55d56 \ --hash=sha256:739e36ef7412b2bd940f75b278749106e6d025e40027c0b94a17ef7968d55d56 \ --hash=sha256:7429e7faa1a60cad26ae4227f4dd0459efde239e494c7312624ce228e04f6391 \ - --hash=sha256:7429e7faa1a60cad26ae4227f4dd0459efde239e494c7312624ce228e04f6391 \ --hash=sha256:74da9f97daec6928567b48c90ea2c82a106b2d500f397eeb8941e47d30b1ca85 \ - --hash=sha256:74da9f97daec6928567b48c90ea2c82a106b2d500f397eeb8941e47d30b1ca85 \ - --hash=sha256:74e4f025ef3db1c6da4460dd27c118d8cd136d0391da4e387a15e48e5c975147 \ --hash=sha256:74e4f025ef3db1c6da4460dd27c118d8cd136d0391da4e387a15e48e5c975147 \ --hash=sha256:75a9632f1d4f698b2e6e2e1ada40e71f369b15d69baddb8968dcc8e683839b18 \ - --hash=sha256:75a9632f1d4f698b2e6e2e1ada40e71f369b15d69baddb8968dcc8e683839b18 \ - --hash=sha256:76acba4c66c47d27c8365e7c10b3d8016a7da83d3191d053a58382311a8bf4e1 \ --hash=sha256:76acba4c66c47d27c8365e7c10b3d8016a7da83d3191d053a58382311a8bf4e1 \ --hash=sha256:79d1fb9252e7e2cfe4de6e9a6610c7cbb99b9708e2c3e29057f487de5a9eaefa \ - --hash=sha256:79d1fb9252e7e2cfe4de6e9a6610c7cbb99b9708e2c3e29057f487de5a9eaefa \ --hash=sha256:7ce7ad8abebe737ad6143d9d3bf94b88b93365ea30a5b81f6877ec9c0dee0a48 \ - --hash=sha256:7ce7ad8abebe737ad6143d9d3bf94b88b93365ea30a5b81f6877ec9c0dee0a48 \ - --hash=sha256:7ed07b3062b055d7a7f9d6557a251cc655eed0b3152b76de619516621c56f5d3 \ --hash=sha256:7ed07b3062b055d7a7f9d6557a251cc655eed0b3152b76de619516621c56f5d3 \ --hash=sha256:8268cbcd48c5375f46e000adb1390572c98879eb4f77910c6053d25cc3ac2c67 \ - --hash=sha256:8268cbcd48c5375f46e000adb1390572c98879eb4f77910c6053d25cc3ac2c67 \ - --hash=sha256:875a3f90d7eb5c5d77e529080d95140eacb3c6d13ad5b616ee8095447b1d22e7 \ --hash=sha256:875a3f90d7eb5c5d77e529080d95140eacb3c6d13ad5b616ee8095447b1d22e7 \ --hash=sha256:89feb82ca055af0fe797a2323ec9043b26bc371365847dbe83c7fd2e2f181c34 \ - --hash=sha256:89feb82ca055af0fe797a2323ec9043b26bc371365847dbe83c7fd2e2f181c34 \ - --hash=sha256:8a7e24cb69ee5f32e003f50e016d5fde438010c1022c96738b04fc2423e61706 \ --hash=sha256:8a7e24cb69ee5f32e003f50e016d5fde438010c1022c96738b04fc2423e61706 \ --hash=sha256:8ab6a358d1286498d80fe67bd3d69fcbc7d1359b45b41e74c4a26964ca99c3f8 \ - --hash=sha256:8ab6a358d1286498d80fe67bd3d69fcbc7d1359b45b41e74c4a26964ca99c3f8 \ - --hash=sha256:8b8df03a9e995b6211dafa63b32f9d405881518ff1ddd775db4e7b98fb545e1c \ --hash=sha256:8b8df03a9e995b6211dafa63b32f9d405881518ff1ddd775db4e7b98fb545e1c \ --hash=sha256:8cf85a6e40ff1f37fe0f25719aadf443686b1ac7652593dc53c7ef9b8492b115 \ - --hash=sha256:8cf85a6e40ff1f37fe0f25719aadf443686b1ac7652593dc53c7ef9b8492b115 \ --hash=sha256:8e8d351ff44c1638cb6e980623d517abd9f580d2e53bfcd18d8941c052a5a009 \ - --hash=sha256:8e8d351ff44c1638cb6e980623d517abd9f580d2e53bfcd18d8941c052a5a009 \ - --hash=sha256:9164361769b6ca7769079f4d426a41df6164879f7f3568be9086e15baca61466 \ --hash=sha256:9164361769b6ca7769079f4d426a41df6164879f7f3568be9086e15baca61466 \ --hash=sha256:96e85aa09274955bb6bd483eaf5b12abadade01010478154b0ec70284c1b1526 \ - --hash=sha256:96e85aa09274955bb6bd483eaf5b12abadade01010478154b0ec70284c1b1526 \ - --hash=sha256:981a06a3076997adf7c743dcd0d7a0415582661e2517c7d961493572e909aa1d \ --hash=sha256:981a06a3076997adf7c743dcd0d7a0415582661e2517c7d961493572e909aa1d \ --hash=sha256:9cd5323344d8ebb9fb5e96da5de5ad4ebab993bbf51674259dbe9d7a18049525 \ - --hash=sha256:9cd5323344d8ebb9fb5e96da5de5ad4ebab993bbf51674259dbe9d7a18049525 \ --hash=sha256:9d6c6ea6a11ca0ff9cd0390b885984ed31157c168565702959c25e2191674a14 \ - --hash=sha256:9d6c6ea6a11ca0ff9cd0390b885984ed31157c168565702959c25e2191674a14 \ - --hash=sha256:a02d3c48f9bb1e10c7788d92c0c7db6f2002d024ab6e74d6f45ae33e3d0288a3 \ --hash=sha256:a02d3c48f9bb1e10c7788d92c0c7db6f2002d024ab6e74d6f45ae33e3d0288a3 \ --hash=sha256:a233bb68625a85126ac9f1fc66d24337d6e8a0f9207b688eec2e7c880f012ec0 \ - --hash=sha256:a233bb68625a85126ac9f1fc66d24337d6e8a0f9207b688eec2e7c880f012ec0 \ - --hash=sha256:a2f6a1bc2460e643785a2cde17293bd7a8f990884b822f7bca47bee0a82fc66b \ --hash=sha256:a2f6a1bc2460e643785a2cde17293bd7a8f990884b822f7bca47bee0a82fc66b \ --hash=sha256:a6d17e0370d2516d5bb9062c7b4cb731cff921fc875644c3d751ad857ba9c5b1 \ - --hash=sha256:a6d17e0370d2516d5bb9062c7b4cb731cff921fc875644c3d751ad857ba9c5b1 \ --hash=sha256:ab67ed772c584b7ef2379797bf14b82df9aa5f7438c5b9a09624dd834c1c1aaf \ - --hash=sha256:ab67ed772c584b7ef2379797bf14b82df9aa5f7438c5b9a09624dd834c1c1aaf \ - --hash=sha256:ac6540c9fff6e3813d29d0403ee7a81897f1d8ecc09a8ff84d2eea70ede1cdbf \ --hash=sha256:ac6540c9fff6e3813d29d0403ee7a81897f1d8ecc09a8ff84d2eea70ede1cdbf \ --hash=sha256:ae4073a60ab98529ab8a72ebf429f2a8cc612619a8c04e08bed27450d52103c0 \ - --hash=sha256:ae4073a60ab98529ab8a72ebf429f2a8cc612619a8c04e08bed27450d52103c0 \ - --hash=sha256:ae791f6bd43305aade8c0e22f816b34f3b72b6c820477aab4d18473a37e8090b \ --hash=sha256:ae791f6bd43305aade8c0e22f816b34f3b72b6c820477aab4d18473a37e8090b \ --hash=sha256:aef5474d913d3b05e613906ba4090433c515e13ea49c837aca18bde190853dff \ - --hash=sha256:aef5474d913d3b05e613906ba4090433c515e13ea49c837aca18bde190853dff \ - --hash=sha256:b0b3f2df149efb242cee2ffdeb6674b7f30d23c9a7af26595099afaf46ef4e88 \ --hash=sha256:b0b3f2df149efb242cee2ffdeb6674b7f30d23c9a7af26595099afaf46ef4e88 \ --hash=sha256:b128092c927eaf485928cec0c28f6b8bead277e28acf56800e972aa2c2abd7a2 \ - --hash=sha256:b128092c927eaf485928cec0c28f6b8bead277e28acf56800e972aa2c2abd7a2 \ - --hash=sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40 \ --hash=sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40 \ --hash=sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716 \ - --hash=sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716 \ --hash=sha256:b47633251727c8fe279f34025844b3b3a3e40cd1b198356d003aa146258d13a2 \ - --hash=sha256:b47633251727c8fe279f34025844b3b3a3e40cd1b198356d003aa146258d13a2 \ - --hash=sha256:b537bd04d7ccd7c6350cdaaaad911f6312cbd61e6e6045542f781c7f8b2e99d2 \ --hash=sha256:b537bd04d7ccd7c6350cdaaaad911f6312cbd61e6e6045542f781c7f8b2e99d2 \ --hash=sha256:b5e4ef22ff25bfd4ede5f8fb30f7b24446345f3e79d9b7455aef2836437bc38a \ - --hash=sha256:b5e4ef22ff25bfd4ede5f8fb30f7b24446345f3e79d9b7455aef2836437bc38a \ - --hash=sha256:b74b9ea10063efb77a965a8d5f4182806fbf59ed068b3c3fd6f30d2ac7bee734 \ --hash=sha256:b74b9ea10063efb77a965a8d5f4182806fbf59ed068b3c3fd6f30d2ac7bee734 \ --hash=sha256:bb2dc4898180bea79863d5487e5f9c7c34297414bad54bcd0f0852aee9cfdb87 \ - --hash=sha256:bb2dc4898180bea79863d5487e5f9c7c34297414bad54bcd0f0852aee9cfdb87 \ --hash=sha256:bbc4b80af581e18568ff07f6395c02114d05f4865c2812a1f02f2eaecf0bfd48 \ - --hash=sha256:bbc4b80af581e18568ff07f6395c02114d05f4865c2812a1f02f2eaecf0bfd48 \ - --hash=sha256:bcc98f911f10278d1daf14b87d65325851a1d29153caaf146877ec37031d5f36 \ --hash=sha256:bcc98f911f10278d1daf14b87d65325851a1d29153caaf146877ec37031d5f36 \ --hash=sha256:be49ad33819d7dcc28a309b86d4ed98e1a65f3075c6acd3cd4fe32103235222b \ - --hash=sha256:be49ad33819d7dcc28a309b86d4ed98e1a65f3075c6acd3cd4fe32103235222b \ - --hash=sha256:bec4bd9133420c5c52d562469c754f27c5c9e36ee06abc169612c959bd7dbb07 \ --hash=sha256:bec4bd9133420c5c52d562469c754f27c5c9e36ee06abc169612c959bd7dbb07 \ --hash=sha256:c689d0d5381f56de7bd6966a4541bff6e08bf8d3871bbd89a0c6ab18aa699573 \ - --hash=sha256:c689d0d5381f56de7bd6966a4541bff6e08bf8d3871bbd89a0c6ab18aa699573 \ --hash=sha256:c7079d5eb1c1315a858bbf180000757db8ad904a89476653232db835c3114001 \ - --hash=sha256:c7079d5eb1c1315a858bbf180000757db8ad904a89476653232db835c3114001 \ - --hash=sha256:cb3942960f0beb9f46e2a71a3aca220d1ca32feb5a398656be934320804c0df9 \ --hash=sha256:cb3942960f0beb9f46e2a71a3aca220d1ca32feb5a398656be934320804c0df9 \ --hash=sha256:cd9e78285da6c9ba2d5c769628f43ef66d96ac3085e59b10ad4f3707980710d3 \ - --hash=sha256:cd9e78285da6c9ba2d5c769628f43ef66d96ac3085e59b10ad4f3707980710d3 \ - --hash=sha256:cf2a978c795b54c539f47964ec05e35c05bd045db5ca1e8366988c7f2fe6b3ce \ --hash=sha256:cf2a978c795b54c539f47964ec05e35c05bd045db5ca1e8366988c7f2fe6b3ce \ --hash=sha256:d14a0d029a4e176795cef99c056d58067c06195e0c7e2dbb293bf95c08f772a3 \ - --hash=sha256:d14a0d029a4e176795cef99c056d58067c06195e0c7e2dbb293bf95c08f772a3 \ - --hash=sha256:d237ba6664b8e60fd90b8549a149a74fcc675272e0e95539a00522e4ca688b04 \ --hash=sha256:d237ba6664b8e60fd90b8549a149a74fcc675272e0e95539a00522e4ca688b04 \ --hash=sha256:d26a618ae1766279f2660aca0081b2220aca6bd1aa06b2cf73f07383faf48927 \ - --hash=sha256:d26a618ae1766279f2660aca0081b2220aca6bd1aa06b2cf73f07383faf48927 \ - --hash=sha256:d28cb356f119a437cc58a13f8135ab8a4c8ece18159eb9194b0d269ec4e28083 \ --hash=sha256:d28cb356f119a437cc58a13f8135ab8a4c8ece18159eb9194b0d269ec4e28083 \ --hash=sha256:d4ed0c7cbecde7194cd3228c044e86bf73e30a23505af852857c09c24e77ec5d \ - --hash=sha256:d4ed0c7cbecde7194cd3228c044e86bf73e30a23505af852857c09c24e77ec5d \ --hash=sha256:d83e2d94b69bf31ead2fa45f0acdef0757fa0458a129734f59f67f3d2eb7ef32 \ - --hash=sha256:d83e2d94b69bf31ead2fa45f0acdef0757fa0458a129734f59f67f3d2eb7ef32 \ - --hash=sha256:d8bbcd21769594dbba9c37d3c819e2d5847656ca99c747ddb31ac1701d0c0ed9 \ --hash=sha256:d8bbcd21769594dbba9c37d3c819e2d5847656ca99c747ddb31ac1701d0c0ed9 \ --hash=sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f \ - --hash=sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f \ - --hash=sha256:dc911208b18842a3a57266d8e51fc3cfaccee90a5351b92079beed912a7914c2 \ --hash=sha256:dc911208b18842a3a57266d8e51fc3cfaccee90a5351b92079beed912a7914c2 \ --hash=sha256:dfa7c241073d8f2b8e8dbc7803c434f57dbb83ae2a3d7892dd068d99e96efe2c \ - --hash=sha256:dfa7c241073d8f2b8e8dbc7803c434f57dbb83ae2a3d7892dd068d99e96efe2c \ --hash=sha256:e282aedd63c639c07c3857097fc0e236f984ceb4089a8b284da1c526491e3f3d \ - --hash=sha256:e282aedd63c639c07c3857097fc0e236f984ceb4089a8b284da1c526491e3f3d \ - --hash=sha256:e290d79a4107d7d794634ce3e985b9ae4f920380a813717adf61804904dc4393 \ --hash=sha256:e290d79a4107d7d794634ce3e985b9ae4f920380a813717adf61804904dc4393 \ --hash=sha256:e3d9d13603410b72787579769469af730c38f2f25505573a5888a94b62b920f8 \ - --hash=sha256:e3d9d13603410b72787579769469af730c38f2f25505573a5888a94b62b920f8 \ - --hash=sha256:e481bba1e11ba585fb06db666bfc23dbe181dbafc7b25776156120bf12e0d5a6 \ --hash=sha256:e481bba1e11ba585fb06db666bfc23dbe181dbafc7b25776156120bf12e0d5a6 \ --hash=sha256:e49b052b768bb74f58c7dda4e0bdf7b79d43a9204ca584ffe1fb48a6f3c84c66 \ - --hash=sha256:e49b052b768bb74f58c7dda4e0bdf7b79d43a9204ca584ffe1fb48a6f3c84c66 \ --hash=sha256:eb00b549b13bd6d884c863554566095bf6fa9c3cecb2e7b399c4bc7904cb33b5 \ - --hash=sha256:eb00b549b13bd6d884c863554566095bf6fa9c3cecb2e7b399c4bc7904cb33b5 \ - --hash=sha256:ec87c44f619380878bd49ca109669c9f221d9ae6883a5bcb3616785fa8f94c97 \ --hash=sha256:ec87c44f619380878bd49ca109669c9f221d9ae6883a5bcb3616785fa8f94c97 \ --hash=sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196 \ - --hash=sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196 \ - --hash=sha256:f11ae142f3a322d44513de1018b50f474f8f736bc3cd91d969f464b5bfef8836 \ --hash=sha256:f11ae142f3a322d44513de1018b50f474f8f736bc3cd91d969f464b5bfef8836 \ --hash=sha256:f2a09f6184f17a80897172863a655467da2b11151ec98ba8d7af89f17bf63dae \ - --hash=sha256:f2a09f6184f17a80897172863a655467da2b11151ec98ba8d7af89f17bf63dae \ - --hash=sha256:f5b65529bb2f21ac7861a0e94fdbf5dc0daab41497d18223b46ee8515e5ad297 \ --hash=sha256:f5b65529bb2f21ac7861a0e94fdbf5dc0daab41497d18223b46ee8515e5ad297 \ --hash=sha256:f60fdd125d85bf9c279ffb8e94c78c51b3b6a37711464e1f5f31078b45002421 \ - --hash=sha256:f60fdd125d85bf9c279ffb8e94c78c51b3b6a37711464e1f5f31078b45002421 \ - --hash=sha256:f61efaf4bed1cc0860e567d2ecb2363974d414f7f1f124b1df368bbf183453a6 \ --hash=sha256:f61efaf4bed1cc0860e567d2ecb2363974d414f7f1f124b1df368bbf183453a6 \ --hash=sha256:f90e552ecbad426eab352e7b2933091f2be77115bb16f09f78404861c8322981 \ - --hash=sha256:f90e552ecbad426eab352e7b2933091f2be77115bb16f09f78404861c8322981 \ --hash=sha256:f956196ef61369f1685d14dad80611488d8dc1ef00be57c0c5a03064005b0f30 \ - --hash=sha256:f956196ef61369f1685d14dad80611488d8dc1ef00be57c0c5a03064005b0f30 \ - --hash=sha256:fb91819461b1b56d06fa4bcf86617fac795f6a99d12239fb0c68dbeba41a0a30 \ --hash=sha256:fb91819461b1b56d06fa4bcf86617fac795f6a99d12239fb0c68dbeba41a0a30 \ --hash=sha256:fbc9d316552f9ef7bba39f4edfad4a734d3d6f93341232a9dddadec4f15d425f \ - --hash=sha256:fbc9d316552f9ef7bba39f4edfad4a734d3d6f93341232a9dddadec4f15d425f \ - --hash=sha256:ff69a9a0b4b17d78170c73abe2ab12084bdf1691550c5629ad1fe7849433f324 \ --hash=sha256:ff69a9a0b4b17d78170c73abe2ab12084bdf1691550c5629ad1fe7849433f324 \ - --hash=sha256:ffb2be176fed4457e445fe540617f0252a72a8bc56208fd65a690fdb1f57660b \ --hash=sha256:ffb2be176fed4457e445fe540617f0252a72a8bc56208fd65a690fdb1f57660b # via # -r requirements/main.in @@ -1249,6 +1138,10 @@ mako==1.3.3 \ --hash=sha256:5324b88089a8978bf76d1629774fcc2f1c07b82acdf00f4c5dd8ceadfffc4b40 \ --hash=sha256:e16c01d9ab9c11f7290eef1cfefc093fb5a45ee4a3da09e2fec2e4d1bae54e73 # via alembic +markdown-it-py==3.0.0 \ + --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ + --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb + # via rich markupsafe==2.1.5 \ --hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \ --hash=sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff \ @@ -1316,6 +1209,10 @@ markupsafe==2.1.5 \ # pyramid-jinja2 # werkzeug # wtforms +mdurl==0.1.2 \ + --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ + --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba + # via markdown-it-py mistune==3.0.2 \ --hash=sha256:71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205 \ --hash=sha256:fc7f93ded930c92394ef2cb6f04a8aabab4117a91449e72dcc8dfa646a508be8 @@ -1383,6 +1280,98 @@ msgpack==1.0.8 \ --hash=sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d \ --hash=sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d # via -r requirements/main.in +multidict==6.0.5 \ + --hash=sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556 \ + --hash=sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c \ + --hash=sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29 \ + --hash=sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b \ + --hash=sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8 \ + --hash=sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7 \ + --hash=sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd \ + --hash=sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40 \ + --hash=sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6 \ + --hash=sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3 \ + --hash=sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c \ + --hash=sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9 \ + --hash=sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5 \ + --hash=sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae \ + --hash=sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442 \ + --hash=sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9 \ + --hash=sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc \ + --hash=sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c \ + --hash=sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea \ + --hash=sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5 \ + --hash=sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50 \ + --hash=sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182 \ + --hash=sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453 \ + --hash=sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e \ + --hash=sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600 \ + --hash=sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733 \ + --hash=sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda \ + --hash=sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241 \ + --hash=sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461 \ + --hash=sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e \ + --hash=sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e \ + --hash=sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b \ + --hash=sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e \ + --hash=sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7 \ + --hash=sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386 \ + --hash=sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd \ + --hash=sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9 \ + --hash=sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf \ + --hash=sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee \ + --hash=sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5 \ + --hash=sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a \ + --hash=sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271 \ + --hash=sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54 \ + --hash=sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4 \ + --hash=sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496 \ + --hash=sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb \ + --hash=sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319 \ + --hash=sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3 \ + --hash=sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f \ + --hash=sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527 \ + --hash=sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed \ + --hash=sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604 \ + --hash=sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef \ + --hash=sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8 \ + --hash=sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5 \ + --hash=sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5 \ + --hash=sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626 \ + --hash=sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c \ + --hash=sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d \ + --hash=sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c \ + --hash=sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc \ + --hash=sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc \ + --hash=sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b \ + --hash=sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38 \ + --hash=sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450 \ + --hash=sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1 \ + --hash=sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f \ + --hash=sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3 \ + --hash=sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755 \ + --hash=sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226 \ + --hash=sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a \ + --hash=sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046 \ + --hash=sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf \ + --hash=sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479 \ + --hash=sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e \ + --hash=sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1 \ + --hash=sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a \ + --hash=sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83 \ + --hash=sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929 \ + --hash=sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93 \ + --hash=sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a \ + --hash=sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c \ + --hash=sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44 \ + --hash=sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89 \ + --hash=sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba \ + --hash=sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e \ + --hash=sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da \ + --hash=sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24 \ + --hash=sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423 \ + --hash=sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef + # via grpclib natsort==8.4.0 \ --hash=sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581 \ --hash=sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c @@ -1517,6 +1506,10 @@ plaster-pastedeploy==1.0.1 \ --hash=sha256:ad3550cc744648969ed3b810f33c9344f515ee8d8a8cec18e8f2c4a643c2181f \ --hash=sha256:be262e6d2e41a7264875daa2fe2850cbb0615728bcdc92828fdc72736e381412 # via pyramid +platformdirs==4.2.1 \ + --hash=sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf \ + --hash=sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1 + # via sigstore premailer==3.10.0 \ --hash=sha256:021b8196364d7df96d04f9ade51b794d0b77bcc19e998321c515633a2273be1a \ --hash=sha256:d1875a8411f5dc92b53ef9f193db6c0f879dc378d618e0ad292723e388bfe4c2 @@ -1559,6 +1552,7 @@ pyasn1==0.6.0 \ # via # pyasn1-modules # rsa + # sigstore pyasn1-modules==0.4.0 \ --hash=sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6 \ --hash=sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b @@ -1608,10 +1602,15 @@ pycurl==7.45.3 \ # -r requirements/main.in # celery # kombu -pydantic==2.7.1 \ +pydantic[email]==2.7.1 \ --hash=sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5 \ --hash=sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc - # via -r requirements/main.in + # via + # -r requirements/main.in + # id + # pypi-attestation-models + # sigstore + # sigstore-rekor-types pydantic-core==2.18.2 \ --hash=sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b \ --hash=sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a \ @@ -1696,13 +1695,16 @@ pydantic-core==2.18.2 \ pygments==2.18.0 \ --hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \ --hash=sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a - # via readme-renderer + # via + # readme-renderer + # rich pyjwt[crypto]==2.8.0 \ --hash=sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de \ --hash=sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320 # via # -r requirements/main.in # pyjwt + # sigstore pymacaroons==0.13.0 \ --hash=sha256:1e6bba42a5f66c245adf38a5a4006a99dcc06a0703786ea636098667d42903b8 \ --hash=sha256:3e14dff6a262fdbf1a15e769ce635a8aea72e6f8f91e408f9a97166c53b91907 @@ -1722,11 +1724,17 @@ pynacl==1.5.0 \ pyopenssl==24.1.0 \ --hash=sha256:17ed5be5936449c5418d1cd269a1a9e9081bc54c17aed272b45856a3d3dc86ad \ --hash=sha256:cabed4bfaa5df9f1a16c0ef64a0cb65318b5cd077a7eda7d6970131ca2f41a6f - # via webauthn + # via + # sigstore + # webauthn pyparsing==3.1.2 \ --hash=sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad \ --hash=sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742 # via linehaul +pypi-attestation-models==0.0.1rc2 \ + --hash=sha256:01670cd63c93943bfe8eb161fc6dcad4c52c4830dce5e583057a2db0813e73a8 \ + --hash=sha256:25277c4a7dcca68ace17bbf6559e3b39f338363ed571cf1f26b2643a7fd248bc + # via -r requirements/main.in pyqrcode==1.2.1 \ --hash=sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6 \ --hash=sha256:fdbf7634733e56b72e27f9bce46e4550b75a3a2c420414035cae9d9d26b234d5 @@ -1775,6 +1783,7 @@ python-dateutil==2.9.0.post0 \ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 # via + # betterproto # botocore # celery # celery-redbeat @@ -1873,10 +1882,13 @@ requests==2.31.0 \ # google-api-core # google-cloud-bigquery # google-cloud-storage + # id # jsonschema-path # premailer # requests-aws4auth + # sigstore # stripe + # tuf requests-aws4auth==1.2.3 \ --hash=sha256:8070a5207e95fa5fe88e87d9a75f34e768cbab35bb3557ef20cbbf9426dee4d5 \ --hash=sha256:d4c73c19f37f80d4aa9c5bd4fa376cfd0c69299c48b00a8eb2ae6b0416164fb8 @@ -1889,6 +1901,14 @@ rfc3986==2.0.0 \ --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \ --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c # via -r requirements/main.in +rfc8785==0.1.2 \ + --hash=sha256:4668c70e02f4cf96327d60375d2f1f21958830ff777e0e414424a794323521a1 \ + --hash=sha256:c4e92e9ecc828bef2aa7dba1de8ac983511f7532a0df11c770d39099a25cf201 + # via sigstore +rich==13.7.1 \ + --hash=sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222 \ + --hash=sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432 + # via sigstore rpds-py==0.18.1 \ --hash=sha256:05f3d615099bd9b13ecf2fc9cf2d839ad3f20239c678f461c753e93755d629ee \ --hash=sha256:06d218939e1bf2ca50e6b0ec700ffe755e5216a8230ab3e87c059ebb4ea06afc \ @@ -2000,10 +2020,28 @@ s3transfer==0.7.0 \ --hash=sha256:10d6923c6359175f264811ef4bf6161a3156ce8e350e705396a7557d6293c33a \ --hash=sha256:fd3889a66f5fe17299fe75b82eae6cf722554edca744ca5d5fe308b104883d2e # via boto3 +securesystemslib==0.31.0 \ + --hash=sha256:549d70f7be6460252d016f03edc5ec0128fee56af55d2b863a5db14541ddbf18 \ + --hash=sha256:c1594afbcd5db198ec90c487e1720154afb71743d9f4bccf3dfda84de650c478 + # via tuf sentry-sdk==2.1.1 \ --hash=sha256:95d8c0bb41c8b0bc37ab202c2c4a295bb84398ee05f4cdce55051cd75b926ec1 \ --hash=sha256:99aeb78fb76771513bd3b2829d12613130152620768d00cd3e45ac00cb17950f # via -r requirements/main.in +sigstore==3.0.0rc2 \ + --hash=sha256:821bfa58425b29225a84b82b1137edc7c8d28028e173732c3abb85e0a64b13de \ + --hash=sha256:ed04fb9fc24901986c3ca5a4372e83a8b1560faa014cb73e640326ab94aa43ad + # via + # -r requirements/main.in + # pypi-attestation-models +sigstore-protobuf-specs==0.3.2 \ + --hash=sha256:50c99fa6747a3a9c5c562a43602cf76df0b199af28f0e9d4319b6775630425ea \ + --hash=sha256:cae041b40502600b8a633f43c257695d0222a94efa1e5110a7ec7ada78c39d99 + # via sigstore +sigstore-rekor-types==0.0.13 \ + --hash=sha256:377fee942d5fc66437a4f54599472157149affaece9bbc7deb05e5b42f34ceba \ + --hash=sha256:63e9306a26931ed74411911948c250da7c5adc51c53507227738170424e6ae2d + # via sigstore six==1.16.0 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 @@ -2109,6 +2147,10 @@ trove-classifiers==2024.4.10 \ --hash=sha256:49f40bb6a746b72a1cba4f8d55ee8252169cda0f70802e3fd24f04b7fb25a492 \ --hash=sha256:678bd6fcc5218d72e3304e27a608acc9b91e17bd00c3f3d8c968497c843ad98b # via -r requirements/main.in +tuf==4.0.0 \ + --hash=sha256:87df89e5e50b0bf85ec84b7aabdac41a54cddaa8c6ce85ac5fae38ea786a12f6 \ + --hash=sha256:a22ab5fa6daf910b3052929fdce42ccad8a300e5e85715daaff9592aed980f7a + # via sigstore typing-extensions==4.11.0 \ --hash=sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0 \ --hash=sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a diff --git a/tests/unit/forklift/test_legacy.py b/tests/unit/forklift/test_legacy.py index 0b6a61d7d629..4e9039941476 100644 --- a/tests/unit/forklift/test_legacy.py +++ b/tests/unit/forklift/test_legacy.py @@ -23,7 +23,14 @@ import pretend import pytest +from pypi_attestation_models import ( + Attestation, + InvalidAttestationError, + VerificationError, + VerificationMaterial, +) from pyramid.httpexceptions import HTTPBadRequest, HTTPForbidden, HTTPTooManyRequests +from sigstore.verify import Verifier from sqlalchemy import and_, exists from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import joinedload @@ -2385,6 +2392,82 @@ def test_upload_fails_without_oidc_publisher_permission( "See /the/help/url/ for more information." ).format(project.name) + def test_upload_attestation_fails_without_oidc_publisher( + self, + monkeypatch, + pyramid_config, + db_request, + metrics, + project_service, + macaroon_service, + ): + project = ProjectFactory.create() + owner = UserFactory.create() + maintainer = UserFactory.create() + RoleFactory.create(user=owner, project=project, role_name="Owner") + RoleFactory.create(user=maintainer, project=project, role_name="Maintainer") + + EmailFactory.create(user=maintainer) + db_request.user = maintainer + raw_macaroon, macaroon = macaroon_service.create_macaroon( + "fake location", + "fake description", + [caveats.RequestUser(user_id=str(maintainer.id))], + user_id=maintainer.id, + ) + identity = UserTokenContext(maintainer, macaroon) + + filename = "{}-{}.tar.gz".format(project.name, "1.0") + attestation = Attestation( + version=1, + verification_material=VerificationMaterial( + certificate="some_cert", transparency_entries=[dict()] + ), + message_signature="some_signature", + ) + + pyramid_config.testing_securitypolicy(identity=identity) + db_request.POST = MultiDict( + { + "metadata_version": "1.2", + "name": project.name, + "attestations": f"[{attestation.model_dump_json()}]", + "version": "1.0", + "filetype": "sdist", + "md5_digest": _TAR_GZ_PKG_MD5, + "content": pretend.stub( + filename=filename, + file=io.BytesIO(_TAR_GZ_PKG_TESTDATA), + type="application/tar", + ), + } + ) + + storage_service = pretend.stub(store=lambda path, filepath, meta: None) + extract_http_macaroon = pretend.call_recorder(lambda r, _: raw_macaroon) + monkeypatch.setattr( + security_policy, "_extract_http_macaroon", extract_http_macaroon + ) + + db_request.find_service = lambda svc, name=None, context=None: { + IFileStorage: storage_service, + IMacaroonService: macaroon_service, + IMetricsService: metrics, + IProjectService: project_service, + }.get(svc) + db_request.user_agent = "warehouse-tests/6.6.6" + + with pytest.raises(HTTPBadRequest) as excinfo: + legacy.file_upload(db_request) + + resp = excinfo.value + + assert resp.status_code == 400 + assert resp.status == ( + "400 Attestations are currently only supported when using Trusted " + "Publishing with GitHub Actions." + ) + @pytest.mark.parametrize( "plat", [ @@ -3293,6 +3376,233 @@ def test_upload_succeeds_creates_release( ), ] + def test_upload_with_valid_attestation_succeeds( + self, + monkeypatch, + pyramid_config, + db_request, + metrics, + ): + from warehouse.events.models import HasEvents + + project = ProjectFactory.create() + version = "1.0" + publisher = GitHubPublisherFactory.create(projects=[project]) + claims = { + "sha": "somesha", + "repository": f"{publisher.repository_owner}/{publisher.repository_name}", + "workflow": "workflow_name", + } + identity = PublisherTokenContext(publisher, SignedClaims(claims)) + db_request.oidc_publisher = identity.publisher + db_request.oidc_claims = identity.claims + + db_request.db.add(Classifier(classifier="Environment :: Other Environment")) + db_request.db.add(Classifier(classifier="Programming Language :: Python")) + + filename = "{}-{}.tar.gz".format(project.name, "1.0") + attestation = Attestation( + version=1, + verification_material=VerificationMaterial( + certificate="somebase64string", transparency_entries=[dict()] + ), + message_signature="somebase64string", + ) + + pyramid_config.testing_securitypolicy(identity=identity) + db_request.user = None + db_request.user_agent = "warehouse-tests/6.6.6" + db_request.POST = MultiDict( + { + "metadata_version": "1.2", + "name": project.name, + "attestations": f"[{attestation.model_dump_json()}]", + "version": version, + "summary": "This is my summary!", + "filetype": "sdist", + "md5_digest": _TAR_GZ_PKG_MD5, + "content": pretend.stub( + filename=filename, + file=io.BytesIO(_TAR_GZ_PKG_TESTDATA), + type="application/tar", + ), + } + ) + + storage_service = pretend.stub(store=lambda path, filepath, meta: None) + db_request.find_service = lambda svc, name=None, context=None: { + IFileStorage: storage_service, + IMetricsService: metrics, + }.get(svc) + + record_event = pretend.call_recorder( + lambda self, *, tag, request=None, additional: None + ) + monkeypatch.setattr(HasEvents, "record_event", record_event) + + verify = pretend.call_recorder(lambda _self, _verifier, _policy, _dist: None) + monkeypatch.setattr(Attestation, "verify", verify) + monkeypatch.setattr(Verifier, "production", lambda: pretend.stub()) + + resp = legacy.file_upload(db_request) + + assert resp.status_code == 200 + + assert len(verify.calls) == 1 + + def test_upload_with_malformed_attestation_fails( + self, + monkeypatch, + pyramid_config, + db_request, + metrics, + ): + from warehouse.events.models import HasEvents + + project = ProjectFactory.create() + version = "1.0" + publisher = GitHubPublisherFactory.create(projects=[project]) + claims = { + "sha": "somesha", + "repository": f"{publisher.repository_owner}/{publisher.repository_name}", + "workflow": "workflow_name", + } + identity = PublisherTokenContext(publisher, SignedClaims(claims)) + db_request.oidc_publisher = identity.publisher + db_request.oidc_claims = identity.claims + + db_request.db.add(Classifier(classifier="Environment :: Other Environment")) + db_request.db.add(Classifier(classifier="Programming Language :: Python")) + + filename = "{}-{}.tar.gz".format(project.name, "1.0") + + pyramid_config.testing_securitypolicy(identity=identity) + db_request.user = None + db_request.user_agent = "warehouse-tests/6.6.6" + db_request.POST = MultiDict( + { + "metadata_version": "1.2", + "name": project.name, + "attestations": "[{'a_malformed_attestation': 3}]", + "version": version, + "summary": "This is my summary!", + "filetype": "sdist", + "md5_digest": _TAR_GZ_PKG_MD5, + "content": pretend.stub( + filename=filename, + file=io.BytesIO(_TAR_GZ_PKG_TESTDATA), + type="application/tar", + ), + } + ) + + storage_service = pretend.stub(store=lambda path, filepath, meta: None) + db_request.find_service = lambda svc, name=None, context=None: { + IFileStorage: storage_service, + IMetricsService: metrics, + }.get(svc) + + record_event = pretend.call_recorder( + lambda self, *, tag, request=None, additional: None + ) + monkeypatch.setattr(HasEvents, "record_event", record_event) + + def failing_verify(_self, _verifier, _policy, _dist): + raise InvalidAttestationError + + monkeypatch.setattr(Attestation, "verify", failing_verify) + monkeypatch.setattr(Verifier, "production", lambda: pretend.stub()) + + with pytest.raises(HTTPBadRequest) as excinfo: + legacy.file_upload(db_request) + + resp = excinfo.value + + assert resp.status_code == 400 + assert resp.status.startswith( + "400 Error while decoding the included attestation:" + ) + + def test_upload_with_failing_attestation_fails( + self, + monkeypatch, + pyramid_config, + db_request, + metrics, + ): + from warehouse.events.models import HasEvents + + project = ProjectFactory.create() + version = "1.0" + publisher = GitHubPublisherFactory.create(projects=[project]) + claims = { + "sha": "somesha", + "repository": f"{publisher.repository_owner}/{publisher.repository_name}", + "workflow": "workflow_name", + } + identity = PublisherTokenContext(publisher, SignedClaims(claims)) + db_request.oidc_publisher = identity.publisher + db_request.oidc_claims = identity.claims + + db_request.db.add(Classifier(classifier="Environment :: Other Environment")) + db_request.db.add(Classifier(classifier="Programming Language :: Python")) + + filename = "{}-{}.tar.gz".format(project.name, "1.0") + attestation = Attestation( + version=1, + verification_material=VerificationMaterial( + certificate="somebase64string", transparency_entries=[dict()] + ), + message_signature="somebase64string", + ) + + pyramid_config.testing_securitypolicy(identity=identity) + db_request.user = None + db_request.user_agent = "warehouse-tests/6.6.6" + db_request.POST = MultiDict( + { + "metadata_version": "1.2", + "name": project.name, + "attestations": f"[{attestation.model_dump_json()}]", + "version": version, + "summary": "This is my summary!", + "filetype": "sdist", + "md5_digest": _TAR_GZ_PKG_MD5, + "content": pretend.stub( + filename=filename, + file=io.BytesIO(_TAR_GZ_PKG_TESTDATA), + type="application/tar", + ), + } + ) + + storage_service = pretend.stub(store=lambda path, filepath, meta: None) + db_request.find_service = lambda svc, name=None, context=None: { + IFileStorage: storage_service, + IMetricsService: metrics, + }.get(svc) + + record_event = pretend.call_recorder( + lambda self, *, tag, request=None, additional: None + ) + monkeypatch.setattr(HasEvents, "record_event", record_event) + + def failing_verify(_self, _verifier, _policy, _dist): + raise VerificationError("verification failed") + + monkeypatch.setattr(Attestation, "verify", failing_verify) + monkeypatch.setattr(Verifier, "production", lambda: pretend.stub()) + + with pytest.raises(HTTPBadRequest) as excinfo: + legacy.file_upload(db_request) + + resp = excinfo.value + + assert resp.status_code == 400 + assert resp.status.startswith( + "400 Could not verify the uploaded artifact using the included attestation" + ) + @pytest.mark.parametrize( "version, expected_version", [ diff --git a/tests/unit/oidc/models/test_github.py b/tests/unit/oidc/models/test_github.py index a0bf7826dfec..d8aaadc18327 100644 --- a/tests/unit/oidc/models/test_github.py +++ b/tests/unit/oidc/models/test_github.py @@ -16,6 +16,7 @@ from tests.common.db.oidc import GitHubPublisherFactory, PendingGitHubPublisherFactory from warehouse.oidc import errors +from warehouse.oidc.errors import InvalidPublisherError from warehouse.oidc.models import _core, github @@ -470,6 +471,31 @@ def test_github_publisher_environment_claim(self, truth, claim, valid): check = github.GitHubPublisher.__optional_verifiable_claims__["environment"] assert check(truth, claim, pretend.stub()) is valid + @pytest.mark.parametrize( + ("ref", "sha", "raises"), + [ + ("ref", "sha", False), + (None, "sha", False), + ("ref", None, False), + (None, None, True), + ], + ) + def test_github_publisher_verification_policy(self, ref, sha, raises): + publisher = github.GitHubPublisher( + repository_name="fakerepo", + repository_owner="fakeowner", + repository_owner_id="fakeid", + workflow_filename="fakeworkflow.yml", + environment="", + ) + claims = {"ref": ref, "sha": sha} + + if not raises: + publisher.publisher_verification_policy(claims) + else: + with pytest.raises(InvalidPublisherError): + publisher.publisher_verification_policy(claims) + def test_github_publisher_duplicates_cant_be_created(self, db_request): publisher1 = github.GitHubPublisher( repository_name="repository_name", diff --git a/warehouse/forklift/legacy.py b/warehouse/forklift/legacy.py index e925db39489c..ac10f90e6f4d 100644 --- a/warehouse/forklift/legacy.py +++ b/warehouse/forklift/legacy.py @@ -19,6 +19,7 @@ import zipfile from cgi import FieldStorage +from pathlib import Path import packaging.requirements import packaging.specifiers @@ -29,6 +30,12 @@ import wtforms import wtforms.validators +from pydantic import TypeAdapter, ValidationError +from pypi_attestation_models import ( + Attestation, + InvalidAttestationError, + VerificationError, +) from pyramid.httpexceptions import ( HTTPBadRequest, HTTPException, @@ -39,6 +46,7 @@ HTTPTooManyRequests, ) from pyramid.view import view_config +from sigstore.verify import Verifier from sqlalchemy import and_, exists, func, orm from sqlalchemy.exc import MultipleResultsFound, NoResultFound @@ -1065,6 +1073,47 @@ def file_upload(request): k: h.hexdigest().lower() for k, h in metadata_file_hashes.items() } + # Check that if the file was uploaded with attestations, verification + # passes + if "attestations" in request.POST: + publisher = request.oidc_publisher + if not publisher or not publisher.publisher_name == "GitHub": + raise _exc_with_message( + HTTPBadRequest, + "Attestations are currently only supported when using Trusted " + "Publishing with GitHub Actions.", + ) + try: + attestations = TypeAdapter(list[Attestation]).validate_json( + request.POST["attestations"] + ) + for attestation_model in attestations: + # For now, attestations are not stored, just verified + verification_policy = publisher.publisher_verification_policy( + request.oidc_claims + ) + attestation_model.verify( + Verifier.production(), + verification_policy, + Path(temporary_filename), + ) + except (ValidationError, InvalidAttestationError) as e: + raise _exc_with_message( + HTTPBadRequest, + f"Error while decoding the included attestation: {e}", + ) + except VerificationError as e: + raise _exc_with_message( + HTTPBadRequest, + f"Could not verify the uploaded artifact using the included " + f"attestation: {e}", + ) + except Exception as e: + raise _exc_with_message( + HTTPBadRequest, + f"Unknown error while trying to verify included attestations: {e}", + ) + # TODO: This should be handled by some sort of database trigger or a # SQLAlchemy hook or the like instead of doing it inline in this # view. diff --git a/warehouse/oidc/models/_core.py b/warehouse/oidc/models/_core.py index 336dddf130b9..9405b600d422 100644 --- a/warehouse/oidc/models/_core.py +++ b/warehouse/oidc/models/_core.py @@ -17,6 +17,7 @@ import sentry_sdk +from sigstore.verify.policy import VerificationPolicy from sqlalchemy import ForeignKey, String, orm from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Mapped, mapped_column @@ -245,6 +246,17 @@ def publisher_url( # Only concrete subclasses are constructed. raise NotImplementedError + def publisher_verification_policy( + self, claims: SignedClaims + ) -> VerificationPolicy: # pragma: no cover + """ + Get the policy used to verify attestations signed with this publisher. + NOTE: This is **NOT** a `@property` because we pass `claims` to it. + When calling, make sure to use `publisher_verification_policy()` + """ + # Only concrete subclasses are constructed. + raise NotImplementedError + def stored_claims( self, claims: SignedClaims | None = None ) -> dict: # pragma: no cover diff --git a/warehouse/oidc/models/github.py b/warehouse/oidc/models/github.py index 840039355a8e..9388c5e84b46 100644 --- a/warehouse/oidc/models/github.py +++ b/warehouse/oidc/models/github.py @@ -12,6 +12,12 @@ from typing import Any +from sigstore.verify.policy import ( + AllOf, + AnyOf, + OIDCBuildConfigURI, + OIDCSourceRepositoryURI, +) from sqlalchemy import ForeignKey, String, UniqueConstraint from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Query, mapped_column @@ -235,6 +241,25 @@ def publisher_url(self, claims=None): return f"{base}/commit/{sha}" return base + def publisher_verification_policy(self, claims): + sha = claims.get("sha") if claims else None + ref = claims.get("ref") if claims else None + if not (ref or sha): + raise InvalidPublisherError("The ref and sha claims are empty") + + expected_build_configs = [ + OIDCBuildConfigURI(f"https://github.com/{self.job_workflow_ref}@{claim}") + for claim in [ref, sha] + if claim is not None + ] + + return AllOf( + [ + OIDCSourceRepositoryURI(f"https://github.com/{self.repository}"), + AnyOf(expected_build_configs), + ], + ) + def stored_claims(self, claims=None): claims = claims if claims else {} return {"ref": claims.get("ref"), "sha": claims.get("sha")}